feat: ui updates
This commit is contained in:
@@ -18,10 +18,43 @@ import {
|
||||
Eye,
|
||||
ShoppingCart,
|
||||
Info,
|
||||
Bot,
|
||||
Settings,
|
||||
Folder,
|
||||
Archive,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { DashboardHeader } from "@/components/layout/dashboard-header";
|
||||
import { useWorkspace } from "@/components/providers/workspace-provider";
|
||||
import { demoAwareAnalyticsApi } from "@/lib/demo";
|
||||
import { demoAwareAnalyticsApi, demoAwareProjectsApi } from "@/lib/demo";
|
||||
import { projectsApi } from "@/lib/api";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -31,6 +64,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import {
|
||||
Table,
|
||||
@@ -58,7 +92,7 @@ interface ProjectWithStats extends Project {
|
||||
total_cost: number;
|
||||
total_subscriptions: number;
|
||||
total_views: number;
|
||||
avg_cps: number | null;
|
||||
avg_cpf: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,11 +122,93 @@ const formatCurrency = (value: number | null | undefined): string => {
|
||||
export default function ProjectsPage() {
|
||||
const params = useParams();
|
||||
const workspaceId = params?.workspaceId as string;
|
||||
const { projects, isLoadingProjects, currentWorkspace } = useWorkspace();
|
||||
const { projects, isLoadingProjects, currentWorkspace, refreshProjects } = useWorkspace();
|
||||
|
||||
const [projectsWithStats, setProjectsWithStats] = useState<ProjectWithStats[]>([]);
|
||||
const [loadingStats, setLoadingStats] = useState(false);
|
||||
const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
|
||||
const [settingsProject, setSettingsProject] = useState<Project | null>(null);
|
||||
const [inviteLinkType, setInviteLinkType] = useState<"public" | "approval">("public");
|
||||
const [isUpdatingSettings, setIsUpdatingSettings] = useState(false);
|
||||
const [isArchiving, setIsArchiving] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [deleteConfirmationText, setDeleteConfirmationText] = useState("");
|
||||
const { isDemoMode } = useWorkspace();
|
||||
|
||||
const handleOpenSettings = (project: Project) => {
|
||||
setSettingsProject(project);
|
||||
setInviteLinkType(project.purchase_invite_type_default || "public");
|
||||
};
|
||||
|
||||
const handleSaveSettings = async () => {
|
||||
if (!settingsProject) return;
|
||||
|
||||
try {
|
||||
setIsUpdatingSettings(true);
|
||||
const api = isDemoMode ? demoAwareProjectsApi : projectsApi;
|
||||
await api.updateInviteLinkType(workspaceId, settingsProject.id, {
|
||||
purchase_invite_type_default: inviteLinkType,
|
||||
});
|
||||
await refreshProjects();
|
||||
setSettingsProject(null);
|
||||
} catch (err) {
|
||||
console.error("Failed to update settings:", err);
|
||||
} finally {
|
||||
setIsUpdatingSettings(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleArchive = async () => {
|
||||
if (!settingsProject) return;
|
||||
|
||||
try {
|
||||
setIsArchiving(true);
|
||||
const api = isDemoMode ? demoAwareProjectsApi : projectsApi;
|
||||
await api.archive(workspaceId, settingsProject.id);
|
||||
await refreshProjects();
|
||||
setSettingsProject(null);
|
||||
} catch (err) {
|
||||
console.error("Failed to archive project:", err);
|
||||
} finally {
|
||||
setIsArchiving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!settingsProject) return;
|
||||
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
const api = isDemoMode ? demoAwareProjectsApi : projectsApi;
|
||||
await api.delete(workspaceId, settingsProject.id);
|
||||
await refreshProjects();
|
||||
setSettingsProject(null);
|
||||
setShowDeleteDialog(false);
|
||||
setDeleteConfirmationText("");
|
||||
} catch (err) {
|
||||
console.error("Failed to delete project:", err);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenDeleteDialog = () => {
|
||||
setShowDeleteDialog(true);
|
||||
};
|
||||
|
||||
const handleCloseDeleteDialog = () => {
|
||||
setShowDeleteDialog(false);
|
||||
setDeleteConfirmationText("");
|
||||
};
|
||||
|
||||
const getChannelAvatar = (project: Project) => {
|
||||
// Telegram channel avatar URL format
|
||||
if (project.username) {
|
||||
return `https://t.me/i/userpic/320/${project.username}.jpg`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Load statistics for each project
|
||||
useEffect(() => {
|
||||
@@ -115,7 +231,7 @@ export default function ProjectsPage() {
|
||||
total_cost: spending.total_cost,
|
||||
total_subscriptions: spending.total_subscriptions,
|
||||
total_views: spending.total_views,
|
||||
avg_cps:
|
||||
avg_cpf:
|
||||
spending.total_subscriptions > 0
|
||||
? spending.total_cost / spending.total_subscriptions
|
||||
: null,
|
||||
@@ -180,18 +296,34 @@ export default function ProjectsPage() {
|
||||
|
||||
<Alert>
|
||||
<Tv className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Чтобы добавить новый проект, добавьте бота{" "}
|
||||
<a
|
||||
href={`https://t.me/${BOT_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-mono text-primary hover:underline inline-flex items-center gap-1"
|
||||
<AlertDescription className="flex items-center justify-between flex-wrap gap-3">
|
||||
<span>
|
||||
Чтобы добавить новый проект, добавьте бота{" "}
|
||||
<a
|
||||
href={`https://t.me/${BOT_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-mono text-primary hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
@{BOT_USERNAME}
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>{" "}
|
||||
администратором в ваш Telegram канал.
|
||||
</span>
|
||||
<Button
|
||||
asChild
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
>
|
||||
@{BOT_USERNAME}
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>{" "}
|
||||
администратором в ваш Telegram канал.
|
||||
<a
|
||||
href={`tg://resolve?domain=${BOT_USERNAME}&startchannel&admin=invite_users+post_messages+edit_messages+delete_messages`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Bot className="h-4 w-4 mr-2" />
|
||||
Добавить бота
|
||||
</a>
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -220,20 +352,20 @@ export default function ProjectsPage() {
|
||||
<TableHead>Статус</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Потрачено
|
||||
Подписчиков
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Сумма всех размещений по проекту
|
||||
Количество подписчиков в канале
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Подписчики
|
||||
Привлечено подписчиков
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
@@ -246,7 +378,7 @@ export default function ProjectsPage() {
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Просмотры
|
||||
Суммарный охват
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
@@ -259,18 +391,18 @@ export default function ProjectsPage() {
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
Сред. CPS
|
||||
CPF
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Cost Per Subscription — средняя стоимость подписчика
|
||||
Cost Per Follower — средняя стоимость подписчика
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead className="w-[140px]"></TableHead>
|
||||
<TableHead className="w-[200px]"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -278,9 +410,12 @@ export default function ProjectsPage() {
|
||||
<TableRow key={project.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Tv className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src={getChannelAvatar(project) || undefined} alt={project.title} />
|
||||
<AvatarFallback className="rounded-lg bg-primary/10">
|
||||
<Tv className="h-4 w-4 text-primary" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="font-medium">{project.title}</div>
|
||||
{project.username && (
|
||||
@@ -308,8 +443,8 @@ export default function ProjectsPage() {
|
||||
: "Архив"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium">
|
||||
{formatCurrency(project.stats?.total_cost)}
|
||||
<TableCell className="text-right">
|
||||
{formatNumber(project.subscribers_count)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatNumber(project.stats?.total_subscriptions)}
|
||||
@@ -318,16 +453,34 @@ export default function ProjectsPage() {
|
||||
{formatNumber(project.stats?.total_views)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency(project.stats?.avg_cps)}
|
||||
{formatCurrency(project.stats?.avg_cpf)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`/dashboard/${currentWorkspace?.id}/projects/${project.id}/purchase-plan`}
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`/dashboard/${currentWorkspace?.id}/purchase-plans/${project.id}`}
|
||||
>
|
||||
План закупов
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`/dashboard/${currentWorkspace?.id}/creatives?project_id=${project.id}`}
|
||||
>
|
||||
<Folder className="h-3 w-3 mr-1" />
|
||||
Креативы
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleOpenSettings(project)}
|
||||
>
|
||||
План закупа
|
||||
</Link>
|
||||
</Button>
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
Настройки
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -342,9 +495,12 @@ export default function ProjectsPage() {
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Tv className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src={getChannelAvatar(project) || undefined} alt={project.title} />
|
||||
<AvatarFallback className="rounded-lg bg-primary/10">
|
||||
<Tv className="h-5 w-5 text-primary" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<CardTitle className="text-base">{project.title}</CardTitle>
|
||||
{project.username && (
|
||||
@@ -375,82 +531,222 @@ export default function ProjectsPage() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Stats Grid */}
|
||||
{project.stats && (
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShoppingCart className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatCurrency(project.stats.total_cost)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Потрачено
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
{project.subscribers_count !== undefined && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatNumber(project.stats.total_subscriptions)}
|
||||
{formatNumber(project.subscribers_count)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Подписчиков
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Eye className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatNumber(project.stats.total_views)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Просмотров
|
||||
)}
|
||||
{project.stats && (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatNumber(project.stats.total_subscriptions)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Привлечено подписчиков
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatCurrency(project.stats.avg_cps)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Сред. CPS
|
||||
<div className="flex items-center gap-2">
|
||||
<Eye className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatNumber(project.stats.total_views)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Суммарный охват
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{formatCurrency(project.stats.avg_cpf)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
CPF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
{project.username && (
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href={`https://t.me/${project.username}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
Канал
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`/dashboard/${currentWorkspace?.id}/projects/${project.id}/purchase-plan`}
|
||||
href={`/dashboard/${currentWorkspace?.id}/purchase-plans/${project.id}`}
|
||||
>
|
||||
План закупа
|
||||
План закупов
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link
|
||||
href={`/dashboard/${currentWorkspace?.id}/creatives?project_id=${project.id}`}
|
||||
>
|
||||
<Folder className="h-4 w-4 mr-1" />
|
||||
Креативы
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleOpenSettings(project)}
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-1" />
|
||||
Настройки
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Settings Dialog */}
|
||||
<Dialog open={!!settingsProject} onOpenChange={(open) => !open && setSettingsProject(null)}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Настройки проекта</DialogTitle>
|
||||
<DialogDescription>
|
||||
{settingsProject?.title}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-link-type">Тип ссылки по умолчанию</Label>
|
||||
<Select
|
||||
value={inviteLinkType}
|
||||
onValueChange={(value) => setInviteLinkType(value as "public" | "approval")}
|
||||
>
|
||||
<SelectTrigger id="invite-link-type">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="public">Открытая</SelectItem>
|
||||
<SelectItem value="approval">С заявками</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Тип пригласительной ссылки, который будет использоваться по умолчанию при создании размещений для этого проекта
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Danger Zone */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-semibold text-destructive">Опасная зона</h4>
|
||||
<div className="flex flex-col gap-2">
|
||||
{settingsProject?.status !== "archived" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleArchive}
|
||||
disabled={isArchiving || isDeleting || isUpdatingSettings}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
{isArchiving ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
Архивирование...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Archive className="h-4 w-4 mr-2" />
|
||||
Архивировать проект
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleOpenDeleteDialog}
|
||||
disabled={isArchiving || isDeleting || isUpdatingSettings}
|
||||
className="w-full justify-start"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Удалить проект
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setSettingsProject(null)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSaveSettings}
|
||||
disabled={isUpdatingSettings || isArchiving || isDeleting}
|
||||
>
|
||||
{isUpdatingSettings ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
Сохранение...
|
||||
</>
|
||||
) : (
|
||||
"Сохранить"
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={handleCloseDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Удалить проект "{settingsProject?.title}"?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Это действие необратимо. Все данные проекта будут удалены, включая креативы, размещения и аналитику.
|
||||
<br /><br />
|
||||
Для подтверждения введите <strong>Удалить {settingsProject?.title}</strong> ниже:
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Input
|
||||
placeholder={`Удалить ${settingsProject?.title}`}
|
||||
value={deleteConfirmationText}
|
||||
onChange={(e) => setDeleteConfirmationText(e.target.value)}
|
||||
className="mt-4"
|
||||
/>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={handleCloseDeleteDialog}>
|
||||
Отмена
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting || deleteConfirmationText !== `Удалить ${settingsProject?.title}`}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
Удаление...
|
||||
</>
|
||||
) : (
|
||||
"Удалить"
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user