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

@@ -45,7 +45,22 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { Channel } from "@/lib/types/api";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useWorkspace } from "@/components/providers/workspace-provider";
import type { Channel, Project } from "@/lib/types/api";
// ============================================================================
// Helpers
@@ -70,6 +85,7 @@ type SortOrder = "asc" | "desc" | null;
export default function ChannelsCatalogPage() {
const params = useParams();
const workspaceId = params?.workspaceId as string;
const { currentProject, projects } = useWorkspace();
const [channels, setChannels] = useState<Channel[]>([]);
const [loading, setLoading] = useState(true);
@@ -78,6 +94,11 @@ export default function ChannelsCatalogPage() {
const [sortField, setSortField] = useState<SortField | null>(null);
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
// Placement dialog
const [selectedChannel, setSelectedChannel] = useState<Channel | null>(null);
const [showPlacementDialog, setShowPlacementDialog] = useState(false);
const [placementProjectId, setPlacementProjectId] = useState<string>("");
useEffect(() => {
const loadChannels = async () => {
try {
@@ -126,6 +147,23 @@ export default function ChannelsCatalogPage() {
return <ArrowUpDown className="h-4 w-4 ml-1" />;
};
const handleOpenPlacement = (channel: Channel) => {
setSelectedChannel(channel);
if (currentProject) {
setPlacementProjectId(currentProject.id);
}
setShowPlacementDialog(true);
};
const handleConfirmPlacement = () => {
if (!placementProjectId || !selectedChannel) return;
window.location.href = `/dashboard/${workspaceId}/purchase-plans/${placementProjectId}?openPlacement=true&channel_id=${selectedChannel.id}`;
};
const activeProjects = useMemo(() => {
return projects.filter(p => p.status === "active");
}, [projects]);
// Filter and sort channels
const filteredChannels = useMemo(() => {
let result = [...channels];
@@ -317,10 +355,8 @@ export default function ChannelsCatalogPage() {
</div>
</TableCell>
<TableCell>
<Button variant="outline" size="sm" asChild>
<a href={`/dashboard/${workspaceId}/placements/new?channel_id=${channel.id}`}>
Разместить
</a>
<Button variant="outline" size="sm" onClick={() => handleOpenPlacement(channel)}>
Разместить
</Button>
</TableCell>
</TableRow>
@@ -361,23 +397,65 @@ export default function ChannelsCatalogPage() {
{channel.description}
</p>
)}
<div className="flex items-center justify-between">
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Users className="h-4 w-4" />
{formatNumber(channel.subscribers_count)}
</div>
<Button variant="outline" size="sm" asChild>
<a href={`/dashboard/${workspaceId}/placements/new?channel_id=${channel.id}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Users className="h-4 w-4" />
{formatNumber(channel.subscribers_count)}
</div>
<Button variant="outline" size="sm" onClick={() => handleOpenPlacement(channel)}>
Разместить
</a>
</Button>
</div>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
<Dialog open={showPlacementDialog} onOpenChange={setShowPlacementDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Создание размещения</DialogTitle>
<DialogDescription>
Выберите проект для размещения на канале {selectedChannel?.title}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label className="text-sm font-medium">Проект</label>
<Select value={placementProjectId} onValueChange={setPlacementProjectId}>
<SelectTrigger>
<SelectValue placeholder="Выберите проект" />
</SelectTrigger>
<SelectContent>
{activeProjects.map((project) => (
<SelectItem key={project.id} value={project.id}>
{project.title}
</SelectItem>
))}
</SelectContent>
</Select>
{activeProjects.length === 0 && (
<p className="text-sm text-muted-foreground">
Нет активных проектов. Создайте проект в настройках.
</p>
)}
</div>
<div className="flex justify-end gap-2 pt-4">
<Button variant="outline" onClick={() => setShowPlacementDialog(false)}>
Отмена
</Button>
<Button
onClick={handleConfirmPlacement}
disabled={!placementProjectId || activeProjects.length === 0}
>
Продолжить
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</TooltipProvider>
);
}