feat: add header channel selector & adding channel modal menu

This commit is contained in:
ivannoskov
2025-12-01 05:57:28 +03:00
parent d4672ea32b
commit d2cc1c39b3
22 changed files with 3473 additions and 892 deletions

View File

@@ -42,11 +42,12 @@ import {
Copy,
Check,
} from "lucide-react";
import type { TargetChannel, ExternalChannel, Creative } from "@/lib/types/api";
import type { ExternalChannel, Creative } from "@/lib/types/api";
import { useTargetChannel } from "@/components/providers/target-channel-provider";
export default function CreatePurchasePage() {
const router = useRouter();
const [channels, setChannels] = useState<TargetChannel[]>([]);
const { selectedChannel } = useTargetChannel();
const [externalChannels, setExternalChannels] = useState<ExternalChannel[]>(
[]
);
@@ -57,7 +58,6 @@ export default function CreatePurchasePage() {
const [copied, setCopied] = useState(false);
const [formData, setFormData] = useState({
target_channel_id: "",
external_channel_id: "",
creative_id: "",
placement_date: "",
@@ -68,41 +68,28 @@ export default function CreatePurchasePage() {
});
useEffect(() => {
loadData();
}, []);
if (selectedChannel) {
loadData();
}
}, [selectedChannel]);
const loadData = async () => {
try {
const channelsRes = await channelsApi.list();
setChannels(channelsRes.target_channels.filter((c) => c.is_active));
if (!selectedChannel) return;
// Загружаем внешние каналы для первого доступного target channel
if (channelsRes.target_channels.length > 0) {
const firstTargetChannel = channelsRes.target_channels[0];
const [externalChannelsRes, creativesRes] = await Promise.all([
externalChannelsApi.list(firstTargetChannel.id),
creativesApi.list(),
]);
setExternalChannels(externalChannelsRes.external_channels);
setCreatives(
creativesRes.creatives.filter((c) => c.status === "active")
);
} else {
setCreatives([]);
setExternalChannels([]);
}
try {
const [externalChannelsRes, creativesRes] = await Promise.all([
externalChannelsApi.list(selectedChannel.id),
creativesApi.list({ target_channel_id: selectedChannel.id }),
]);
setExternalChannels(externalChannelsRes.external_channels);
setCreatives(
creativesRes.creatives.filter((c) => c.status === "active")
);
} catch (err) {
console.error("Error loading data:", err);
}
};
// Фильтр креативов по выбранному целевому каналу
const filteredCreatives = formData.target_channel_id
? creatives.filter(
(c) => c.target_channel_id === formData.target_channel_id
)
: creatives;
const selectedCreative = creatives.find((c) => c.id === formData.creative_id);
const handleSubmit = async (e: React.FormEvent) => {
@@ -110,8 +97,8 @@ export default function CreatePurchasePage() {
setError(null);
// Validation
if (!formData.target_channel_id) {
setError("Выберите целевой канал");
if (!selectedChannel) {
setError("Выберите целевой канал в верхнем меню");
return;
}
@@ -138,7 +125,7 @@ export default function CreatePurchasePage() {
try {
setIsLoading(true);
const result = await purchasesApi.create({
target_channel_id: formData.target_channel_id,
target_channel_id: selectedChannel.id,
external_channel_id: formData.external_channel_id,
creative_id: formData.creative_id,
placement_date: formData.placement_date,
@@ -253,7 +240,6 @@ export default function CreatePurchasePage() {
onClick={() => {
setInviteLink("");
setFormData({
target_channel_id: "",
external_channel_id: "",
creative_id: "",
placement_date: "",
@@ -315,42 +301,29 @@ export default function CreatePurchasePage() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="target_channel_id">Целевой канал *</Label>
<Select
value={formData.target_channel_id}
onValueChange={(value) =>
setFormData({
...formData,
target_channel_id: value,
creative_id: "", // Сбросить креатив при смене канала
})
}
>
<SelectTrigger>
<SelectValue placeholder="Выберите канал" />
</SelectTrigger>
<SelectContent>
{channels.map((channel) => (
<SelectItem key={channel.id} value={channel.id}>
{channel.title}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedChannel && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Размещение будет создано для канала: <strong>{selectedChannel.title}</strong>
</AlertDescription>
</Alert>
)}
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="external_channel_id">Внешний канал *</Label>
<Select
value={formData.external_channel_id}
onValueChange={(value) =>
setFormData({ ...formData, external_channel_id: value })
setFormData({
...formData,
external_channel_id: value,
})
}
>
<SelectTrigger>
<SelectValue placeholder="Выберите канал" />
<SelectValue placeholder="Выберите внешний канал" />
</SelectTrigger>
<SelectContent>
{externalChannels.map((channel) => (
@@ -361,34 +334,27 @@ export default function CreatePurchasePage() {
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="creative_id">Креатив *</Label>
<Select
value={formData.creative_id}
onValueChange={(value) =>
setFormData({ ...formData, creative_id: value })
}
disabled={!formData.target_channel_id}
<div className="space-y-2">
<Label htmlFor="creative_id">Креатив *</Label>
<Select
value={formData.creative_id}
onValueChange={(value) =>
setFormData({ ...formData, creative_id: value })
}
>
<SelectTrigger>
<SelectValue
placeholder={
formData.target_channel_id
? "Выберите креатив"
: "Сначала выберите целевой канал"
}
/>
</SelectTrigger>
<SelectContent>
{filteredCreatives.map((creative) => (
<SelectItem key={creative.id} value={creative.id}>
{creative.name}
</SelectItem>
))}
</SelectContent>
</Select>
<SelectTrigger>
<SelectValue placeholder="Выберите креатив" />
</SelectTrigger>
<SelectContent>
{creatives.map((creative) => (
<SelectItem key={creative.id} value={creative.id}>
{creative.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>