feat: platform update

This commit is contained in:
ivannoskov
2026-02-09 13:10:08 +03:00
parent f7597270e5
commit 1826483750
15 changed files with 907 additions and 2796 deletions

View File

@@ -6,6 +6,7 @@
import { useEffect, useMemo, useState } from "react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
import {
Loader2,
@@ -36,8 +37,6 @@ import { DashboardHeader } from "@/components/layout/dashboard-header";
import { useWorkspace } from "@/components/providers/workspace-provider";
import { demoAwarePlacementsApi } from "@/lib/demo";
import { placementsApi, creativesApi } from "@/lib/api";
import { AddChannelDialog } from "@/components/dialog-add-channel";
import { CreatePlacementsDialog } from "@/components/dialog-create-placements";
import { FiltersModal, PlacementFilters } from "@/components/filters-modal";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
@@ -82,6 +81,7 @@ import { ColumnSelectorDialog } from "@/components/column-selector-dialog";
import { CreativeSelectDialog } from "@/components/creative-select-dialog";
import { PlacementStatusSelector } from "@/components/placement-status-selector";
import { PlacementPostStatusSelector } from "@/components/placement-post-status-selector";
import { PlacementWizard } from "@/components/placement-wizard";
import { cn } from "@/lib/utils";
import type {
Project,
@@ -933,13 +933,13 @@ function ChannelGroup({
{channel.username && (
<div className="flex items-center gap-1 ml-2">
<a
href={`https://telemetr.me/channel/${channel.username}`}
href={`https://telemetr.me/content/${channel.username}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center h-6 w-6 hover:opacity-75 transition-opacity"
onClick={(e) => e.stopPropagation()}
>
<img src="/icons/telemetr.jpg" alt="TgStat" className="h-5 w-5 rounded" />
<Image src="/icons/telemetr.jpg" width={20} height={20} alt="TgStat" className="h-5 w-5 rounded" />
</a>
<a
href={`https://tgstat.ru/channel/@${channel.username}`}
@@ -948,7 +948,7 @@ function ChannelGroup({
className="flex items-center justify-center h-6 w-6 hover:opacity-75 transition-opacity"
onClick={(e) => e.stopPropagation()}
>
<img src="/icons/tgstat.jpg" alt="TgStat" className="h-5 w-5 rounded" />
<Image src="/icons/tgstat.jpg" width={20} height={20} alt="TgStat" className="h-5 w-5 rounded" />
</a>
</div>
)}
@@ -1086,10 +1086,9 @@ export default function PurchasePlanDetailPage() {
const [saveError, setSaveError] = useState<string | null>(null);
const [editingCell, setEditingCell] = useState<EditingCell | null>(null);
// Create placements dialog
// Placement wizard state
const prefilledCreativeId = searchParams.get("creative_id");
const [prefilledChannelIds, setPrefilledChannelIds] = useState<string[] | null>(null);
const [showCreateDialog, setShowCreateDialog] = useState(false);
const [showPlacementWizard, setShowPlacementWizard] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [statusFilter, setStatusFilter] = useState("all");
@@ -1109,6 +1108,9 @@ export default function PurchasePlanDetailPage() {
const [selectedPlacementForCreative, setSelectedPlacementForCreative] = useState<PlacementWithStats | null>(null);
const [creatives, setCreatives] = useState<Array<{ id: string; name: string; thumbnail_url?: string | null }>>([]);
// Placement wizard
const [wizardInitialCreative, setWizardInitialCreative] = useState<string | null>(null);
const loadCreatives = async () => {
try {
const response = await creativesApi.list(workspaceId, {
@@ -1169,19 +1171,15 @@ export default function PurchasePlanDetailPage() {
}
}, [projects, projectId]);
// Auto-open create dialog if creative_id is provided
// Handle URL params for placement wizard
useEffect(() => {
if (prefilledCreativeId && project) {
setShowCreateDialog(true);
const openPlacement = searchParams.get("openPlacement");
if (openPlacement === "true") {
const creativeId = searchParams.get("creative_id");
setWizardInitialCreative(creativeId);
setShowPlacementWizard(true);
}
}, [prefilledCreativeId, project]);
// Auto-open create dialog if channel_ids are provided
useEffect(() => {
if (prefilledChannelIds && project) {
setShowCreateDialog(true);
}
}, [prefilledChannelIds, project]);
}, [searchParams]);
useEffect(() => {
loadPlacements();
@@ -1700,32 +1698,10 @@ export default function PurchasePlanDetailPage() {
</div>
</div>
<div className="flex items-center gap-2">
<AddChannelDialog
workspaceId={workspaceId}
projectId={projectId}
existingPlacements={placements}
onSuccess={loadPlacements}
>
<Button variant="outline" disabled={isDemoMode}>
<Plus className="h-4 w-4 mr-2" />
Добавить канал
</Button>
</AddChannelDialog>
<CreatePlacementsDialog
workspaceId={workspaceId}
projectId={projectId}
existingPlacements={placements}
prefilledCreativeId={prefilledCreativeId}
prefilledChannelIds={prefilledChannelIds}
open={showCreateDialog}
onOpenChange={setShowCreateDialog}
onSuccess={loadPlacements}
>
<Button disabled={isDemoMode}>
<Plus className="h-4 w-4 mr-2" />
Создать размещения
</Button>
</CreatePlacementsDialog>
<Button disabled={isDemoMode} onClick={() => setShowPlacementWizard(true)}>
<Plus className="h-4 w-4 mr-2" />
Добавить размещения
</Button>
</div>
</div>
@@ -1950,6 +1926,15 @@ export default function PurchasePlanDetailPage() {
onClear={selectedPlacementForCreative ? () => handleClearCreative(selectedPlacementForCreative.id) : null}
currentCreativeId={selectedPlacementForCreative?.creative_id}
/>
<PlacementWizard
open={showPlacementWizard}
onOpenChange={setShowPlacementWizard}
workspaceId={workspaceId}
projectId={projectId}
initialCreativeId={wizardInitialCreative}
onSuccess={loadPlacements}
/>
</>
);
}