233 lines
11 KiB
TypeScript
233 lines
11 KiB
TypeScript
import type { PlacementStatus, PlacementPostStatus, CostType, PlacementType, InviteLinkType } from "@/lib/types/api";
|
||
|
||
export interface ColumnConfig {
|
||
id: string;
|
||
label: string;
|
||
group: string;
|
||
editable: boolean;
|
||
partialEdit?: boolean;
|
||
width?: string;
|
||
required?: boolean;
|
||
}
|
||
|
||
export const COLUMN_GROUPS = [
|
||
{ id: "basic", label: "Основная информация" },
|
||
{ id: "content", label: "Контент" },
|
||
{ id: "finance", label: "Финансы" },
|
||
{ id: "dates", label: "Даты и время" },
|
||
{ id: "metrics", label: "Метрики" },
|
||
{ id: "calculated", label: "Расчетные показатели" },
|
||
{ id: "technical", label: "Технические детали" },
|
||
] as const;
|
||
|
||
export const ALL_COLUMNS: ColumnConfig[] = [
|
||
// Basic
|
||
{ id: "checkbox", label: "Чекбокс", group: "basic", editable: false, required: true },
|
||
{ id: "number", label: "Номер", group: "basic", editable: false, required: true },
|
||
{ id: "status_deal", label: "Статус сделки", group: "basic", editable: true, partialEdit: true },
|
||
{ id: "status_post", label: "Статус поста", group: "basic", editable: true, partialEdit: true },
|
||
|
||
// Content
|
||
{ id: "creative", label: "Креатив", group: "content", editable: true },
|
||
{ id: "invite_link", label: "Пригл. ссылка", group: "content", editable: false },
|
||
{ id: "post_link", label: "Ссылка на пост", group: "content", editable: false },
|
||
|
||
// Finance
|
||
{ id: "cost", label: "Стоимость", group: "finance", editable: true, partialEdit: true },
|
||
{ id: "cost_before", label: "Стоимость до торга", group: "finance", editable: true },
|
||
{ id: "cost_format", label: "Формат оплаты", group: "finance", editable: true, partialEdit: true },
|
||
{ id: "placement_type", label: "Тип закупа", group: "finance", editable: true },
|
||
{ id: "discount", label: "% скидки", group: "finance", editable: false },
|
||
{ id: "payment_date", label: "Дата оплаты", group: "finance", editable: true },
|
||
|
||
// Dates
|
||
{ id: "planned_date", label: "Плановая дата", group: "dates", editable: true },
|
||
{ id: "actual_date", label: "Фактическая дата", group: "dates", editable: false },
|
||
{ id: "format", label: "Плановый формат", group: "dates", editable: true },
|
||
{ id: "time_top", label: "Время в топе", group: "dates", editable: false },
|
||
{ id: "time_in_feed", label: "Время в ленте", group: "dates", editable: false },
|
||
{ id: "link_created", label: "Дата ссылки", group: "dates", editable: false },
|
||
{ id: "post_deleted", label: "Дата удаления", group: "dates", editable: false },
|
||
|
||
// Metrics
|
||
{ id: "subscriptions", label: "Всего подписок", group: "metrics", editable: false },
|
||
{ id: "views", label: "Просмотры", group: "metrics", editable: false },
|
||
|
||
// Calculated
|
||
{ id: "cpf", label: "CPF", group: "calculated", editable: false },
|
||
{ id: "cpm", label: "CPM", group: "calculated", editable: true, partialEdit: true },
|
||
{ id: "conversion_24h", label: "Конверсия 24ч", group: "calculated", editable: false },
|
||
{ id: "conversion_48h", label: "Конверсия 48ч", group: "calculated", editable: false },
|
||
{ id: "conversion_total", label: "Конверсия общ.", group: "calculated", editable: false },
|
||
{ id: "total_unsubs", label: "Всего отписок", group: "calculated", editable: false },
|
||
{ id: "unsub_percent", label: "% отписок", group: "calculated", editable: false },
|
||
{ id: "total_active", label: "Итого подписок", group: "calculated", editable: false },
|
||
|
||
// Technical
|
||
{ id: "invite_type", label: "Тип ссылки", group: "technical", editable: true },
|
||
{ id: "comment", label: "Комментарий", group: "technical", editable: true },
|
||
] as const;
|
||
|
||
// Default visible columns
|
||
export const DEFAULT_VISIBLE_COLUMNS = new Set([
|
||
"checkbox",
|
||
"number",
|
||
"status_deal",
|
||
"status_post",
|
||
"creative",
|
||
"invite_link",
|
||
"cost",
|
||
"cost_before",
|
||
"cost_format",
|
||
"planned_date",
|
||
"format",
|
||
"subscriptions",
|
||
"views",
|
||
"cpf",
|
||
"cpm",
|
||
"comment",
|
||
]);
|
||
|
||
// Status lists
|
||
export const PLACEMENT_DEAL_STATUSES: PlacementStatus[] = [
|
||
"Без статуса",
|
||
"Написать",
|
||
"Ждём ответа",
|
||
"Согласование условий",
|
||
"Оплатить",
|
||
"Оплачено",
|
||
"Отмена",
|
||
"Не подходит цена",
|
||
"Неактуально",
|
||
"Не отвечает",
|
||
];
|
||
|
||
export const PLACEMENT_POST_STATUSES: PlacementPostStatus[] = [
|
||
"Без статуса",
|
||
"Отправить пост",
|
||
"Согласование поста",
|
||
"Ожидание отложки",
|
||
"Запланирован",
|
||
"Пост вышел",
|
||
"Размещение отработало - пост удалён",
|
||
"Размещение отработало - пост не удалён",
|
||
"Проверить - пост удалён раньше срока",
|
||
"Проверить - пост не вышел",
|
||
"Размещение отработало",
|
||
];
|
||
|
||
export const COST_FORMATS: CostType[] = ["fixed", "cpm"];
|
||
export const PLACEMENT_TYPES: PlacementType[] = ["self_promo", "standard"];
|
||
export const INVITE_TYPES: InviteLinkType[] = ["public", "approval"];
|
||
|
||
// Manual post statuses (user can select before post is published)
|
||
export const MANUAL_POST_STATUSES: Set<PlacementPostStatus> = new Set([
|
||
"Без статуса",
|
||
"Отправить пост",
|
||
"Согласование поста",
|
||
"Ожидание отложки",
|
||
"Запланирован",
|
||
]);
|
||
|
||
// Automatic post statuses (set by backend)
|
||
export const AUTOMATIC_POST_STATUSES: Set<PlacementPostStatus> = new Set([
|
||
"Пост вышел",
|
||
"Размещение отработало - пост удалён",
|
||
"Размещение отработало - пост не удалён",
|
||
"Проверить - пост удалён раньше срока",
|
||
"Проверить - пост не вышел",
|
||
"Размещение отработало",
|
||
]);
|
||
|
||
// Helper functions
|
||
export const canEditPostStatus = (currentStatus: PlacementPostStatus): boolean => {
|
||
return MANUAL_POST_STATUSES.has(currentStatus);
|
||
};
|
||
|
||
export const isAutomaticPostStatus = (status: PlacementPostStatus): boolean => {
|
||
return AUTOMATIC_POST_STATUSES.has(status);
|
||
};
|
||
|
||
// Status icon and color helpers
|
||
import {
|
||
CircleHelp,
|
||
FileText,
|
||
Clock,
|
||
DollarSign,
|
||
CheckCircle2,
|
||
XCircle,
|
||
AlertTriangle,
|
||
Send,
|
||
Calendar,
|
||
} from "lucide-react";
|
||
|
||
const StatusIconMap: Record<PlacementStatus, any> = {
|
||
"Без статуса": CircleHelp,
|
||
"Написать": FileText,
|
||
"Ждём ответа": Clock,
|
||
"Согласование условий": FileText,
|
||
"Оплатить": DollarSign,
|
||
"Оплачено": CheckCircle2,
|
||
"Отмена": XCircle,
|
||
"Не подходит цена": AlertTriangle,
|
||
"Неактуально": XCircle,
|
||
"Не отвечает": AlertTriangle,
|
||
};
|
||
|
||
const PostStatusIconMap: Record<PlacementPostStatus, any> = {
|
||
"Без статуса": CircleHelp,
|
||
"Отправить пост": Send,
|
||
"Согласование поста": FileText,
|
||
"Ожидание отложки": Clock,
|
||
"Запланирован": Calendar,
|
||
"Пост вышел": CheckCircle2,
|
||
"Размещение отработало - пост удалён": CheckCircle2,
|
||
"Размещение отработало - пост не удалён": CheckCircle2,
|
||
"Проверить - пост удалён раньше срока": AlertTriangle,
|
||
"Проверить - пост не вышел": Clock,
|
||
"Размещение отработало": CheckCircle2,
|
||
};
|
||
|
||
const StatusColorMap: Record<PlacementStatus, string> = {
|
||
"Без статуса": "bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-900/30 dark:text-gray-400 dark:border-gray-800",
|
||
"Написать": "bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800",
|
||
"Ждём ответа": "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-800",
|
||
"Согласование условий": "bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800",
|
||
"Оплатить": "bg-green-100 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800",
|
||
"Оплачено": "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
|
||
"Отмена": "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800",
|
||
"Не подходит цена": "bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-900/30 dark:text-orange-400 dark:border-orange-800",
|
||
"Неактуально": "bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-900/30 dark:text-gray-400 dark:border-gray-800",
|
||
"Не отвечает": "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800",
|
||
};
|
||
|
||
const PostStatusColorMap: Record<PlacementPostStatus, string> = {
|
||
"Без статуса": "bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-900/30 dark:text-gray-400 dark:border-gray-800",
|
||
"Отправить пост": "bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800",
|
||
"Согласование поста": "bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800",
|
||
"Ожидание отложки": "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-800",
|
||
"Запланирован": "bg-cyan-100 text-cyan-700 border-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400 dark:border-cyan-800",
|
||
"Пост вышел": "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
|
||
"Размещение отработало - пост удалён": "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
|
||
"Размещение отработало - пост не удалён": "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
|
||
"Проверить - пост удалён раньше срока": "bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-900/30 dark:text-orange-400 dark:border-orange-800",
|
||
"Проверить - пост не вышел": "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800",
|
||
"Размещение отработало": "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
|
||
};
|
||
|
||
export const getPlacementStatusIcon = (status: PlacementStatus): any => {
|
||
return StatusIconMap[status] || CircleHelp;
|
||
};
|
||
|
||
export const getPlacementStatusColor = (status: PlacementStatus): string => {
|
||
return StatusColorMap[status] || StatusColorMap["Без статуса"];
|
||
};
|
||
|
||
export const getPlacementPostStatusIcon = (status: PlacementPostStatus): any => {
|
||
return PostStatusIconMap[status] || CircleHelp;
|
||
};
|
||
|
||
export const getPlacementPostStatusColor = (status: PlacementPostStatus): string => {
|
||
return PostStatusColorMap[status] || PostStatusColorMap["Без статуса"];
|
||
};
|