feat: update permissions
This commit is contained in:
@@ -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("Размещения созданы", "Успешно добавлено");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user