"use client"; import { useState } from "react"; import { Filter, ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import type { Placement, PlacementStatus, PlacementPostStatus, CostType, PlacementType } from "@/lib/types/api"; type FilterCondition = "contains" | "not_contains" | "equals"; interface FilterField { key: string; label: string; type: "text" | "number" | "date"; } interface SortField { key: string; label: string; } interface SortOption { field: string; direction: "asc" | "desc"; } interface FiltersModalProps { open: boolean; onOpenChange: (open: boolean) => void; onApply: (filters: PlacementFilters) => void; existingPlacements: Placement[]; initialFilters: PlacementFilters; } export interface PlacementFilters { channelFilters: { name: { value: string; condition: FilterCondition }; username: { value: string; condition: FilterCondition }; subscribersMin: number | null; subscribersMax: number | null; }; placementFilters: { statuses: PlacementStatus[]; postStatuses: PlacementPostStatus[]; hasCreative: boolean | null; costMin: number | null; costMax: number | null; costFormat: CostType | null; placementType: PlacementType | null; dateFrom: string | null; dateTo: string | null; }; sort: SortOption | null; } const CHANNEL_FILTER_FIELDS: FilterField[] = [ { key: "name", label: "Название канала", type: "text" }, { key: "username", label: "Username", type: "text" }, ]; const PLACEMENT_SORT_FIELDS: SortField[] = [ { key: "cost", label: "Стоимость" }, { key: "date", label: "Дата размещения" }, { key: "cpm", label: "CPM" }, { key: "cpf", label: "CPF" }, { key: "subscribers", label: "Подписчики" }, { key: "views", label: "Просмотры" }, { key: "created", label: "Дата создания" }, ]; const CONDITION_OPTIONS: { value: FilterCondition; label: string }[] = [ { value: "contains", label: "содержит" }, { value: "not_contains", label: "не содержит" }, { value: "equals", label: "равно" }, ]; const STATUS_OPTIONS: PlacementStatus[] = [ "Без статуса", "Написать", "Ждём ответа", "Согласование условий", "Оплатить", "Оплачено", "Отмена", "Не подходит цена", "Неактуально", "Не отвечает", ]; const POST_STATUS_OPTIONS: PlacementPostStatus[] = [ "Без статуса", "Отправить пост", "Согласование поста", "Ожидание отложки", "Запланирован", "Пост вышел", "Размещение отработало - пост удалён", "Размещение отработало - пост не удалён", "Проверить - пост удалён раньше срока", "Проверить - пост не вышел", "Размещение отработало", ]; const COST_FORMAT_OPTIONS: { value: CostType | "any"; label: string }[] = [ { value: "any", label: "Любой" }, { value: "fixed", label: "Фикс" }, { value: "cpm", label: "CPM" }, ]; const PLACEMENT_TYPE_OPTIONS: { value: PlacementType | "any"; label: string }[] = [ { value: "any", label: "Любой" }, { value: "standard", label: "Стандартный" }, { value: "self_promo", label: "Взаимный пиар" }, ]; export function FiltersModal({ open, onOpenChange, onApply, initialFilters, }: FiltersModalProps) { const [filters, setFilters] = useState(initialFilters); const [activeTab, setActiveTab] = useState<"channels" | "placements">("channels"); const handleApply = () => { onApply(filters); onOpenChange(false); }; const handleReset = () => { const defaultFilters: PlacementFilters = { channelFilters: { name: { value: "", condition: "contains" }, username: { value: "", condition: "contains" }, subscribersMin: null, subscribersMax: null, }, placementFilters: { statuses: [], postStatuses: [], hasCreative: null, costMin: null, costMax: null, costFormat: null, placementType: null, dateFrom: null, dateTo: null, }, sort: null, }; setFilters(defaultFilters); }; const toggleStatus = (status: PlacementStatus) => { const currentStatuses = filters.placementFilters.statuses; const newStatuses = currentStatuses.includes(status) ? currentStatuses.filter((s) => s !== status) : [...currentStatuses, status]; setFilters({ ...filters, placementFilters: { ...filters.placementFilters, statuses: newStatuses }, }); }; const togglePostStatus = (status: PlacementPostStatus) => { const currentStatuses = filters.placementFilters.postStatuses; const newStatuses = currentStatuses.includes(status) ? currentStatuses.filter((s) => s !== status) : [...currentStatuses, status]; setFilters({ ...filters, placementFilters: { ...filters.placementFilters, postStatuses: newStatuses }, }); }; const updateChannelFilter = ( key: "name" | "username", updates: { value?: string; condition?: FilterCondition } ) => { setFilters({ ...filters, channelFilters: { ...filters.channelFilters, [key]: { ...filters.channelFilters[key], ...updates }, }, }); }; const hasActiveFilters = filters.channelFilters.name.value || filters.channelFilters.username.value || filters.channelFilters.subscribersMin !== null || filters.channelFilters.subscribersMax !== null || filters.placementFilters.statuses.length > 0 || filters.placementFilters.postStatuses.length > 0 || filters.placementFilters.hasCreative !== null || filters.placementFilters.costMin !== null || filters.placementFilters.costMax !== null || filters.placementFilters.costFormat !== null || filters.placementFilters.placementType !== null || filters.placementFilters.dateFrom || filters.placementFilters.dateTo || filters.sort; return ( Фильтры Настройте фильтры для каналов и размещений
{activeTab === "channels" && (
{CHANNEL_FILTER_FIELDS.map((field) => (
updateChannelFilter(field.key as "name" | "username", { value: e.target.value }) } />
))}
setFilters({ ...filters, channelFilters: { ...filters.channelFilters, subscribersMin: e.target.value ? parseInt(e.target.value) : null, }, }) } /> setFilters({ ...filters, channelFilters: { ...filters.channelFilters, subscribersMax: e.target.value ? parseInt(e.target.value) : null, }, }) } />

Каналы всегда сортируются по алфавиту

)} {activeTab === "placements" && (
{STATUS_OPTIONS.map((status) => ( ))}
{POST_STATUS_OPTIONS.map((status) => ( ))}
{[ { value: null, label: "Любой" }, { value: true, label: "С креативом" }, { value: false, label: "Без креатива" }, ].map((opt) => ( ))}
setFilters({ ...filters, placementFilters: { ...filters.placementFilters, costMin: e.target.value ? parseFloat(e.target.value) : null, }, }) } /> setFilters({ ...filters, placementFilters: { ...filters.placementFilters, costMax: e.target.value ? parseFloat(e.target.value) : null, }, }) } />
setFilters({ ...filters, placementFilters: { ...filters.placementFilters, dateFrom: e.target.value || null, }, }) } /> setFilters({ ...filters, placementFilters: { ...filters.placementFilters, dateTo: e.target.value || null, }, }) } />
{filters.sort && (
)} {filters.sort && ( )}
)}
); }