feat: update permissions

This commit is contained in:
ivannoskov
2026-02-10 17:41:11 +03:00
parent e1117e46c8
commit f0574db79f
7 changed files with 335 additions and 140 deletions

View File

@@ -84,6 +84,7 @@ import { PlacementPostStatusSelector } from "@/components/placement-post-status-
import { PlacementWizard } from "@/components/placement-wizard";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
import { isPermissionError, showSuccessToast } from "@/lib/utils/error-handler";
import type {
Project,
Placement,
@@ -1361,25 +1362,24 @@ export default function PurchasePlanDetailPage() {
const entries = Object.entries(draftsByPlacementId);
if (entries.length === 0) return;
try {
setSavingEdits(true);
setSaveError(null);
for (const [placementId, draft] of entries) {
await placementsApi.updateInProject(workspaceId, projectId, placementId, draft as UpdatePlacementInProjectRequest);
await toast.promise(
(async () => {
for (const [placementId, draft] of entries) {
await placementsApi.updateInProject(workspaceId, projectId, placementId, draft as UpdatePlacementInProjectRequest);
}
await loadPlacements();
})(),
{
loading: "Сохранение изменений...",
success: "Изменения сохранены",
error: (err) => {
if (isPermissionError(err as any)) {
return "Недостаточно прав. Обратитесь к администратору.";
}
return (err as any)?.detail || "Не удалось сохранить изменения";
},
}
await loadPlacements();
setSavingEdits(false);
} catch (err: unknown) {
console.error("Failed to save placement edits:", err);
const message =
typeof err === "object" && err && "detail" in err
? String((err as any).detail)
: "Не удалось сохранить изменения";
setSaveError(message);
setSavingEdits(false);
}
);
};
const toggleChannelSelection = (channelId: string, placementIds: string[]) => {
@@ -1413,15 +1413,25 @@ export default function PurchasePlanDetailPage() {
const selectedIds = Array.from(selectedPlacementIds);
if (selectedIds.length === 0) return;
try {
for (const placementId of selectedIds) {
await placementsApi.deleteInProject(workspaceId, projectId, placementId);
await toast.promise(
(async () => {
for (const placementId of selectedIds) {
await placementsApi.deleteInProject(workspaceId, projectId, placementId);
}
await loadPlacements();
clearSelection();
})(),
{
loading: "Удаление размещений...",
success: () => `Удалено ${selectedIds.length} размещений`,
error: (err) => {
if (isPermissionError(err as any)) {
return "Недостаточно прав. Обратитесь к администратору.";
}
return (err as any)?.detail || "Не удалось удалить размещения";
},
}
await loadPlacements();
clearSelection();
} catch (err) {
console.error("Failed to delete placements:", err);
}
);
};
const handleCopyPlacements = async () => {
@@ -1429,9 +1439,8 @@ export default function PurchasePlanDetailPage() {
const selectedPlacements = placements.filter((p) => selectedPlacementIds.has(p.id));
if (selectedPlacements.length === 0) return;
try {
setCopyingChannels(true);
await placementsApi.createBulk(workspaceId, targetProjectId, {
await toast.promise(
placementsApi.createBulk(workspaceId, targetProjectId, {
creative_id: undefined,
channels: selectedPlacements.map((p) => ({
channel_id: p.channel.id,
@@ -1447,50 +1456,62 @@ export default function PurchasePlanDetailPage() {
creative_id: p.details.creative_id || undefined,
} : undefined,
})),
});
setShowCopyDialog(false);
setTargetProjectId(null);
clearSelection();
} catch (err) {
console.error("Failed to copy placements:", err);
} finally {
setCopyingChannels(false);
}
}),
{
loading: "Копирование размещений...",
success: () => {
setShowCopyDialog(false);
setTargetProjectId(null);
clearSelection();
return `Скопировано ${selectedPlacements.length} размещений`;
},
error: (err) => {
if (isPermissionError(err as any)) {
return "Недостаточно прав. Обратитесь к администратору.";
}
return (err as any)?.detail || "Не удалось скопировать размещения";
},
}
);
};
const handleAddPlacement = async (channel: Placement["channel"]) => {
if (!canEditPlacements) return;
try {
const result = await placementsApi.createInProject(workspaceId, projectId, {
creative_id: undefined,
channels: [{
channel_id: channel.id,
status: "Без статуса",
comment: undefined,
details: {},
}],
const result = await placementsApi.createInProject(workspaceId, projectId, {
creative_id: undefined,
channels: [{
channel_id: channel.id,
status: "Без статуса",
comment: undefined,
details: {},
});
// Extract the new placement from the response
const newPlacement = result.placements[0];
// Add the new placement to the list and switch to edit mode
setPlacements((prev) => [...prev, newPlacement]);
// Scroll to and focus the new placement after a short delay
setTimeout(() => {
const rowElement = document.querySelector(`[data-placement-id="${newPlacement.id}"]`);
if (rowElement) {
rowElement.scrollIntoView({ behavior: "smooth", block: "center" });
// Set editing state for the first editable field
setEditingCell({ placementId: newPlacement.id, field: "status" });
}],
details: {},
});
toast.promise(Promise.resolve(result), {
loading: "Добавление размещения...",
success: () => {
const newPlacement = result.placements[0];
setPlacements((prev) => [...prev, newPlacement]);
setTimeout(() => {
const rowElement = document.querySelector(`[data-placement-id="${newPlacement.id}"]`);
if (rowElement) {
rowElement.scrollIntoView({ behavior: "smooth", block: "center" });
setEditingCell({ placementId: newPlacement.id, field: "status" });
}
}, 100);
return `Размещение добавлено: ${channel.title}`;
},
error: (err) => {
if (isPermissionError(err as any)) {
return "Недостаточно прав. Обратитесь к администратору.";
}
}, 100);
} catch (err) {
console.error("Failed to add placement:", err);
}
return (err as any)?.detail || "Не удалось добавить размещение";
},
});
};
const groupedPlacements = useMemo(() => {
@@ -1959,7 +1980,10 @@ export default function PurchasePlanDetailPage() {
workspaceId={workspaceId}
projectId={projectId}
initialCreativeId={wizardInitialCreative}
onSuccess={loadPlacements}
onSuccess={() => {
loadPlacements();
showSuccessToast("Размещения созданы", "Успешно добавлено");
}}
/>
</>
);