292 lines
9.5 KiB
TypeScript
292 lines
9.5 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { Eye, EyeOff, Loader2 } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import asanaLogo from "@/modules/intelligence-ia/assets/asana-logo.png";
|
|
|
|
interface Workspace {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
interface User {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
interface AsanaCardProps {
|
|
apiKey?: string;
|
|
workspaces?: Workspace[];
|
|
users?: User[];
|
|
selectedWorkspaceId?: string;
|
|
selectedUserId?: string;
|
|
loadingWorkspaces?: boolean;
|
|
loadingUsers?: boolean;
|
|
loadingIntegration?: boolean;
|
|
hasIntegration?: boolean;
|
|
saving?: boolean;
|
|
onSave?: (apiKey: string, workspaceId: string, userId: string) => void | Promise<void>;
|
|
onConfirmApiKey?: (apiKey: string) => void | Promise<void>;
|
|
onWorkspaceChange?: (workspaceId: string) => void | Promise<void>;
|
|
}
|
|
|
|
export function AsanaCard({
|
|
apiKey = "",
|
|
workspaces = [],
|
|
users = [],
|
|
selectedWorkspaceId = "",
|
|
selectedUserId = "",
|
|
loadingWorkspaces = false,
|
|
loadingUsers = false,
|
|
loadingIntegration = false,
|
|
hasIntegration = false,
|
|
saving = false,
|
|
onSave,
|
|
onConfirmApiKey,
|
|
onWorkspaceChange,
|
|
}: AsanaCardProps) {
|
|
const [showKey, setShowKey] = useState(false);
|
|
const [key, setKey] = useState(apiKey);
|
|
const [selectedWorkspace, setSelectedWorkspace] = useState(selectedWorkspaceId);
|
|
const [selectedUser, setSelectedUser] = useState(selectedUserId);
|
|
const [isApiKeyConfirmed, setIsApiKeyConfirmed] = useState(!!apiKey);
|
|
|
|
// Atualiza os valores quando as props mudam (carregamento inicial)
|
|
useEffect(() => {
|
|
if (apiKey) {
|
|
setKey(apiKey);
|
|
setIsApiKeyConfirmed(true);
|
|
}
|
|
}, [apiKey]);
|
|
|
|
useEffect(() => {
|
|
if (selectedWorkspaceId) {
|
|
setSelectedWorkspace(selectedWorkspaceId);
|
|
}
|
|
}, [selectedWorkspaceId]);
|
|
|
|
useEffect(() => {
|
|
if (selectedUserId) {
|
|
setSelectedUser(selectedUserId);
|
|
}
|
|
}, [selectedUserId]);
|
|
|
|
const handleKeyChange = (value: string) => {
|
|
setKey(value);
|
|
setSelectedWorkspace("");
|
|
setSelectedUser("");
|
|
setIsApiKeyConfirmed(false);
|
|
};
|
|
|
|
const handleConfirmApiKey = async () => {
|
|
if (key.length >= 5) {
|
|
setIsApiKeyConfirmed(true);
|
|
// Chama a função assíncrona para buscar workspaces
|
|
await onConfirmApiKey?.(key);
|
|
}
|
|
};
|
|
|
|
const handleWorkspaceChange = async (value: string) => {
|
|
setSelectedWorkspace(value);
|
|
setSelectedUser("");
|
|
// Chama a função assíncrona para buscar usuários
|
|
await onWorkspaceChange?.(value);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
await onSave?.(key, selectedWorkspace, selectedUser);
|
|
};
|
|
|
|
return (
|
|
<div className="metric-card space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 rounded-xl bg-muted flex items-center justify-center">
|
|
<img src={asanaLogo} alt="Asana" className="w-6 h-6" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-foreground font-medium">Asana</h3>
|
|
<p className="text-muted-foreground text-sm">Gerencie tarefas e projetos</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<label className="text-sm text-muted-foreground mb-1.5 block">
|
|
API Key / Token
|
|
</label>
|
|
<div className="relative">
|
|
{loadingIntegration ? (
|
|
<div className="relative">
|
|
<Skeleton className="h-10 w-full" />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex gap-2">
|
|
<div className="relative flex-1">
|
|
<Input
|
|
type={showKey ? "text" : "password"}
|
|
value={key}
|
|
onChange={(e) => handleKeyChange(e.target.value)}
|
|
placeholder="Insira sua chave de API"
|
|
className="pr-10 bg-card border-border"
|
|
disabled={loadingWorkspaces}
|
|
/>
|
|
<button
|
|
onClick={() => setShowKey(!showKey)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
disabled={loadingWorkspaces}
|
|
>
|
|
{showKey ? (
|
|
<EyeOff className="w-4 h-4" />
|
|
) : (
|
|
<Eye className="w-4 h-4" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={handleConfirmApiKey}
|
|
disabled={key.length < 5 || isApiKeyConfirmed || loadingWorkspaces}
|
|
className="shrink-0"
|
|
>
|
|
{loadingWorkspaces ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
Carregando...
|
|
</>
|
|
) : isApiKeyConfirmed ? (
|
|
"Confirmado"
|
|
) : (
|
|
"Confirmar"
|
|
)}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-sm text-muted-foreground mb-1.5 block">
|
|
Workspace
|
|
</label>
|
|
{loadingIntegration ? (
|
|
<div className="relative">
|
|
<Skeleton className="h-10 w-full" />
|
|
<div className="absolute inset-0 flex items-center justify-center gap-2">
|
|
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
|
|
<span className="text-sm text-muted-foreground">Carregando integração...</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="relative">
|
|
<Select
|
|
value={selectedWorkspace}
|
|
onValueChange={handleWorkspaceChange}
|
|
disabled={loadingWorkspaces || loadingIntegration}
|
|
>
|
|
<SelectTrigger className="bg-card border-border">
|
|
<SelectValue
|
|
placeholder={
|
|
loadingWorkspaces
|
|
? "Carregando workspaces..."
|
|
: workspaces.length === 0
|
|
? "Insira o token e clique em Confirmar"
|
|
: "Selecione um workspace"
|
|
}
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-card border-border">
|
|
{workspaces.map((ws) => (
|
|
<SelectItem key={ws.id} value={ws.id}>
|
|
{ws.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{loadingWorkspaces && (
|
|
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-sm text-muted-foreground mb-1.5 block">
|
|
Usuário
|
|
</label>
|
|
{loadingUsers ? (
|
|
<div className="relative">
|
|
<Skeleton className="h-10 w-full" />
|
|
<div className="absolute inset-0 flex items-center justify-center gap-2">
|
|
<Loader2 className="w-4 h-4 animate-spin text-muted-foreground" />
|
|
<span className="text-sm text-muted-foreground">Carregando usuários...</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="relative">
|
|
<Select
|
|
value={selectedUser}
|
|
onValueChange={setSelectedUser}
|
|
disabled={loadingUsers || !selectedWorkspace}
|
|
>
|
|
<SelectTrigger className="bg-card border-border">
|
|
<SelectValue
|
|
placeholder={
|
|
!selectedWorkspace
|
|
? "Selecione um workspace primeiro"
|
|
: users.length === 0
|
|
? "Nenhum usuário encontrado"
|
|
: "Selecione um usuário"
|
|
}
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-card border-border">
|
|
{users.map((user) => (
|
|
<SelectItem key={user.id} value={user.id}>
|
|
{user.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 pt-2">
|
|
<Button
|
|
size="sm"
|
|
onClick={handleSave}
|
|
disabled={!selectedWorkspace || !selectedUser || saving}
|
|
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
|
>
|
|
{saving ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
{hasIntegration ? "Salvando..." : "Criando..."}
|
|
</>
|
|
) : hasIntegration ? (
|
|
"Salvar"
|
|
) : (
|
|
"Criar Integração"
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|