Refactor: Split InstanceCard into smaller components

Refactors the InstanceCard component into smaller, more manageable components to improve code organization and readability. This includes extracting parts of the UI and logic into separate components.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-20 17:03:04 +00:00
parent d9a804c36a
commit 4da7b28d1a
6 changed files with 256 additions and 130 deletions

View File

@ -0,0 +1,55 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
interface ConfirmationDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
actionLabel: string;
onAction: () => Promise<void>;
loading: boolean;
}
const ConfirmationDialog = ({
open,
onOpenChange,
title,
description,
actionLabel,
onAction,
loading,
}: ConfirmationDialogProps) => {
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={onAction}
disabled={loading}
className={loading ? "opacity-50 cursor-not-allowed" : ""}
>
{loading ? "Processando..." : actionLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default ConfirmationDialog;

View File

@ -0,0 +1,104 @@
import React from "react";
import { Button } from "@/components/ui/button";
import {
QrCode,
RefreshCw,
PowerOff,
CircleDot,
CircleOff,
X
} from "lucide-react";
import { WhatsAppInstance } from "@/types/whatsAppTypes";
interface InstanceActionsProps {
instance: WhatsAppInstance;
loading: string | null;
onViewQrCode: (instance: WhatsAppInstance) => void;
onRestart: () => void;
onLogout: () => void;
onDelete: () => void;
onSetOnline: () => void;
onSetOffline: () => void;
}
const InstanceActions = ({
instance,
loading,
onViewQrCode,
onRestart,
onLogout,
onDelete,
onSetOnline,
onSetOffline,
}: InstanceActionsProps) => {
return (
<>
<div className="flex justify-between w-full">
<Button
variant="outline"
size="sm"
onClick={() => onViewQrCode(instance)}
className="flex items-center"
>
<QrCode className="h-4 w-4 mr-2" />
Ver QR Code
</Button>
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:text-red-700 hover:bg-red-50"
onClick={onDelete}
disabled={loading !== null}
>
<X className="h-4 w-4 mr-2" />
Excluir
</Button>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<Button
variant="outline"
size="sm"
onClick={onRestart}
disabled={loading !== null}
>
<RefreshCw className="h-4 w-4 mr-1" />
Reiniciar
</Button>
<Button
variant="outline"
size="sm"
onClick={onLogout}
disabled={loading !== null}
>
<PowerOff className="h-4 w-4 mr-1" />
Desconectar
</Button>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<Button
variant="outline"
size="sm"
onClick={onSetOnline}
disabled={loading !== null}
className="bg-green-50 hover:bg-green-100 text-green-700"
>
<CircleDot className="h-4 w-4 mr-1" />
Definir Online
</Button>
<Button
variant="outline"
size="sm"
onClick={onSetOffline}
disabled={loading !== null}
className="bg-red-50 hover:bg-red-100 text-red-700"
>
<CircleOff className="h-4 w-4 mr-1" />
Definir Offline
</Button>
</div>
</>
);
};
export default InstanceActions;

View File

@ -1,28 +1,11 @@
import { useState } from 'react'; import { useState } from 'react';
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'; import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Smartphone } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
QrCode,
Smartphone,
RefreshCw,
X,
PowerOff,
CircleDot,
CircleOff
} from 'lucide-react';
import { WhatsAppInstance } from '@/types/whatsAppTypes'; import { WhatsAppInstance } from '@/types/whatsAppTypes';
import { import StatusBadge from './StatusBadge';
AlertDialog, import InstanceActions from './InstanceActions';
AlertDialogAction, import ConfirmationDialog from './ConfirmationDialog';
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
interface InstanceCardProps { interface InstanceCardProps {
instance: WhatsAppInstance; instance: WhatsAppInstance;
@ -175,112 +158,35 @@ const InstanceCard = ({
<span>{instance.phoneNumber}</span> <span>{instance.phoneNumber}</span>
</div> </div>
<div className="flex items-center text-sm"> <div className="flex items-center text-sm">
{connectionStatus === 'open' ? ( <StatusBadge connectionStatus={connectionStatus} />
<Badge variant="outline" className="flex items-center gap-1 text-green-500 border-green-300">
<span className="inline-block w-2 h-2 rounded-full bg-green-500"></span>
Status: Conectado
</Badge>
) : connectionStatus === 'connecting' ? (
<Badge variant="outline" className="flex items-center gap-1 text-orange-500 border-orange-300">
<span className="inline-block w-2 h-2 rounded-full bg-orange-500"></span>
Status: Conectando
</Badge>
) : (
<Badge variant="outline" className="flex items-center gap-1 text-red-500 border-red-300">
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>
Status: Desconectado
</Badge>
)}
</div> </div>
</div> </div>
</CardContent> </CardContent>
<CardFooter className="flex flex-col gap-2"> <CardFooter className="flex flex-col gap-2">
<div className="flex justify-between w-full"> <InstanceActions
<Button instance={instance}
variant="outline" loading={loading}
size="sm" onViewQrCode={onViewQrCode}
onClick={() => onViewQrCode(instance)} onRestart={handleRestart}
className="flex items-center" onLogout={handleLogout}
> onDelete={handleDelete}
<QrCode className="h-4 w-4 mr-2" /> onSetOnline={handleSetOnline}
Ver QR Code onSetOffline={handleSetOffline}
</Button> />
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:text-red-700 hover:bg-red-50"
onClick={handleDelete}
disabled={loading !== null}
>
<X className="h-4 w-4 mr-2" />
Excluir
</Button>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<Button
variant="outline"
size="sm"
onClick={handleRestart}
disabled={loading !== null}
>
<RefreshCw className="h-4 w-4 mr-1" />
Reiniciar
</Button>
<Button
variant="outline"
size="sm"
onClick={handleLogout}
disabled={loading !== null}
>
<PowerOff className="h-4 w-4 mr-1" />
Desconectar
</Button>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<Button
variant="outline"
size="sm"
onClick={handleSetOnline}
disabled={loading !== null}
className="bg-green-50 hover:bg-green-100 text-green-700"
>
<CircleDot className="h-4 w-4 mr-1" />
Definir Online
</Button>
<Button
variant="outline"
size="sm"
onClick={handleSetOffline}
disabled={loading !== null}
className="bg-red-50 hover:bg-red-100 text-red-700"
>
<CircleOff className="h-4 w-4 mr-1" />
Definir Offline
</Button>
</div>
</CardFooter> </CardFooter>
</Card> </Card>
<AlertDialog open={confirmAction?.open} onOpenChange={(open) => !open && setConfirmAction(null)}> {confirmAction && (
<AlertDialogContent> <ConfirmationDialog
<AlertDialogHeader> open={confirmAction.open}
<AlertDialogTitle>{confirmAction?.title}</AlertDialogTitle> onOpenChange={(open) => !open && setConfirmAction(null)}
<AlertDialogDescription> title={confirmAction.title}
{confirmAction?.description} description={confirmAction.description}
</AlertDialogDescription> actionLabel={confirmAction.actionLabel}
</AlertDialogHeader> onAction={executeAction}
<AlertDialogFooter> loading={loading !== null}
<AlertDialogCancel disabled={loading !== null}>Cancelar</AlertDialogCancel> />
<AlertDialogAction )}
onClick={executeAction}
disabled={loading !== null}
className={loading === confirmAction?.title ? "opacity-50 cursor-not-allowed" : ""}
>
{loading === confirmAction?.title ? "Processando..." : confirmAction?.actionLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</> </>
); );
}; };

View File

@ -3,13 +3,31 @@ import React from 'react';
interface LoadingStateProps { interface LoadingStateProps {
message?: string; message?: string;
size?: 'small' | 'medium' | 'large';
className?: string;
} }
const LoadingState = ({ message = "Buscando instância..." }: LoadingStateProps) => { const LoadingState = ({
message = "Buscando instância...",
size = 'medium',
className = ''
}: LoadingStateProps) => {
const spinnerSizes = {
small: 'h-6 w-6',
medium: 'h-12 w-12',
large: 'h-16 w-16'
};
const textSizes = {
small: 'text-sm',
medium: 'text-base',
large: 'text-lg'
};
return ( return (
<div className="flex justify-center items-center p-10"> <div className={`flex justify-center items-center p-4 ${className}`}>
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div> <div className={`animate-spin rounded-full ${spinnerSizes[size]} border-b-2 border-primary`}></div>
<p className="ml-3">{message}</p> {message && <p className={`ml-3 ${textSizes[size]}`}>{message}</p>}
</div> </div>
); );
}; };

View File

@ -0,0 +1,34 @@
import React from "react";
import { Badge } from "@/components/ui/badge";
interface StatusBadgeProps {
connectionStatus: string;
}
const StatusBadge = ({ connectionStatus }: StatusBadgeProps) => {
if (connectionStatus === 'open') {
return (
<Badge variant="outline" className="flex items-center gap-1 text-green-500 border-green-300">
<span className="inline-block w-2 h-2 rounded-full bg-green-500"></span>
Status: Conectado
</Badge>
);
} else if (connectionStatus === 'connecting') {
return (
<Badge variant="outline" className="flex items-center gap-1 text-orange-500 border-orange-300">
<span className="inline-block w-2 h-2 rounded-full bg-orange-500"></span>
Status: Conectando
</Badge>
);
} else {
return (
<Badge variant="outline" className="flex items-center gap-1 text-red-500 border-red-300">
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>
Status: Desconectado
</Badge>
);
}
};
export default StatusBadge;

View File

@ -1,10 +1,14 @@
import { useEffect } from 'react'; import { useEffect, useRef } from 'react';
export const usePeriodicStatusCheck = ( export const usePeriodicStatusCheck = (
instancesCount: number, instancesCount: number,
checkAllInstancesStatus: () => Promise<void> checkAllInstancesStatus: () => Promise<void>,
initialDelay: number = 1000,
checkInterval: number = 30000
) => { ) => {
const isFirstRun = useRef(true);
// Set up periodic status checks // Set up periodic status checks
useEffect(() => { useEffect(() => {
console.log("Setting up periodic status checks, current instances:", instancesCount); console.log("Setting up periodic status checks, current instances:", instancesCount);
@ -15,16 +19,17 @@ export const usePeriodicStatusCheck = (
initialCheck = setTimeout(() => { initialCheck = setTimeout(() => {
console.log("Running initial status check"); console.log("Running initial status check");
checkAllInstancesStatus(); checkAllInstancesStatus();
}, 1000); isFirstRun.current = false;
}, initialDelay);
} }
// Set up interval for periodic checks (every 30 seconds) // Set up interval for periodic checks
const interval = setInterval(() => { const interval = setInterval(() => {
if (instancesCount > 0) { if (instancesCount > 0) {
console.log("Running periodic status check"); console.log("Running periodic status check");
checkAllInstancesStatus(); checkAllInstancesStatus();
} }
}, 30000); // 30 seconds }, checkInterval);
// Clean up interval and timeout when component unmounts // Clean up interval and timeout when component unmounts
return () => { return () => {
@ -33,5 +38,9 @@ export const usePeriodicStatusCheck = (
clearTimeout(initialCheck); clearTimeout(initialCheck);
} }
}; };
}, [instancesCount, checkAllInstancesStatus]); }, [instancesCount, checkAllInstancesStatus, initialDelay, checkInterval]);
return {
isFirstRun: isFirstRun.current
};
}; };