feat: update analytics
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -471,7 +471,7 @@ function CellRenderer({
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{placement.invite_link ? (
|
||||
<code className="text-xs bg-muted/50 px-2 py-1 rounded truncate max-w-[200px]">
|
||||
{placement.invite_link}
|
||||
{placement.invite_link.slice(8, 20)}…
|
||||
</code>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
@@ -498,8 +498,16 @@ function CellRenderer({
|
||||
);
|
||||
|
||||
case "cost":
|
||||
const isFixedFormat = placement.details?.cost?.type === "fixed";
|
||||
const canEditCost = canEdit && isFixedFormat;
|
||||
const costType = placement.details?.cost?.type || "fixed";
|
||||
const costValue = placement.details?.cost?.value || 0;
|
||||
const viewsCount = placement.placement_post?.views_count || 0;
|
||||
const subsCount = placement.placement_post?.subscriptions_count || 0;
|
||||
|
||||
// Если тип CPM - показываем рассчитанную стоимость = CPM * views / 1000
|
||||
// Если тип Fixed - показываем фиксированную стоимость
|
||||
const calculatedCost = costType === "cpm" ? (viewsCount > 0 ? (costValue * viewsCount) / 1000 : null) : costValue;
|
||||
|
||||
const canEditCost = canEdit && costType === "fixed";
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{isEditing && editingCell?.field === "cost" ? (
|
||||
@@ -509,7 +517,7 @@ function CellRenderer({
|
||||
step="0.01"
|
||||
autoFocus
|
||||
className="h-7 min-w-[80px] bg-background px-2 text-xs"
|
||||
defaultValue={effectiveCost}
|
||||
defaultValue={calculatedCost || 0}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
if (!raw) {
|
||||
@@ -533,8 +541,55 @@ function CellRenderer({
|
||||
canEditCost && setEditingCell({ placementId: placement.id, field: "cost" })
|
||||
}
|
||||
>
|
||||
<span>{effectiveCost ? formatCurrency(effectiveCost) : "—"}</span>
|
||||
<Pencil className="h-3 w-3 opacity-0 group-hover:opacity-50 transition-opacity" />
|
||||
<span className="font-medium">{calculatedCost ? formatCurrency(calculatedCost) : "—"}</span>
|
||||
{costType === "fixed" && <Pencil className="h-3 w-3 opacity-0 group-hover:opacity-50 transition-opacity" />}
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
|
||||
case "cpm":
|
||||
// Если тип CPM - показываем CPM значение
|
||||
// Если тип Fixed - рассчитываем CPM = cost / views * 1000
|
||||
const cpmType = placement.details?.cost?.type || "fixed";
|
||||
const cpmValue = placement.details?.cost?.value || 0;
|
||||
const cpmViewsCount = placement.placement_post?.views_count || 0;
|
||||
|
||||
const calculatedCpm = cpmType === "cpm" ? cpmValue : (cpmViewsCount > 0 ? (cpmValue / cpmViewsCount) * 1000 : null);
|
||||
|
||||
const canEditCpm = canEdit && cpmType === "cpm";
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{isEditing && editingCell?.field === "cpm" ? (
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
autoFocus
|
||||
className="h-7 min-w-[80px] bg-background px-2 text-xs"
|
||||
defaultValue={calculatedCpm || 0}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
if (!raw) return;
|
||||
const value = Number(raw);
|
||||
if (Number.isNaN(value)) return;
|
||||
handleDraftChange(placement.id, {
|
||||
cost: { type: "cpm", value },
|
||||
});
|
||||
}}
|
||||
onBlur={() => setEditingCell(null)}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canEditCpm}
|
||||
className="group inline-flex min-h-[1.75rem] w-full items-center justify-between rounded px-2 text-xs text-left hover:bg-muted/50 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-default"
|
||||
onClick={() =>
|
||||
canEditCpm && setEditingCell({ placementId: placement.id, field: "cpm" })
|
||||
}
|
||||
>
|
||||
<span>{calculatedCpm ? formatCurrency(calculatedCpm) : "—"}</span>
|
||||
{cpmType === "cpm" && <Pencil className="h-3 w-3 opacity-0 group-hover:opacity-50 transition-opacity" />}
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
@@ -750,48 +805,6 @@ function CellRenderer({
|
||||
</td>
|
||||
);
|
||||
|
||||
case "cpm":
|
||||
const isCpmFormat = placement.details?.cost?.type === "cpm";
|
||||
const canEditCpm = canEdit && isCpmFormat;
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{isEditing && editingCell?.field === "cpm" ? (
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
autoFocus
|
||||
className="h-7 min-w-[80px] bg-background px-2 text-xs"
|
||||
defaultValue={cpm || 0}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
if (!raw) return;
|
||||
const value = Number(raw);
|
||||
if (Number.isNaN(value)) return;
|
||||
const currentCost = placement.details?.cost;
|
||||
handleDraftChange(placement.id, {
|
||||
cost: { type: "cpm", value: (currentCost?.type === "cpm" ? currentCost.value : 0) },
|
||||
cpm: value,
|
||||
});
|
||||
}}
|
||||
onBlur={() => setEditingCell(null)}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canEditCpm}
|
||||
className="group inline-flex min-h-[1.75rem] w-full items-center justify-between rounded px-2 text-xs text-left hover:bg-muted/50 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-default"
|
||||
onClick={() =>
|
||||
canEditCpm && setEditingCell({ placementId: placement.id, field: "cpm" })
|
||||
}
|
||||
>
|
||||
<span>{cpm ? formatCurrency(cpm) : "—"}</span>
|
||||
<Pencil className="h-3 w-3 opacity-0 group-hover:opacity-50 transition-opacity" />
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
|
||||
case "conversion_24h":
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3 text-muted-foreground text-xs">
|
||||
|
||||
Reference in New Issue
Block a user