import { useEffect, useMemo, useState } from "react"; import type { ApiSettings, AuthInfo, CampaignListItem } from "../../../types"; import { KeyRound, Trash2 } from "lucide-react"; import { fetchResourceAccessExplanation, getCampaignShares, getCampaignShareTargets, revokeCampaignShare, updateCampaignOwner, upsertCampaignShare, type CampaignShare, type CampaignShareTargets, type ResourceAccessExplanationResponse } from "../../../api/campaigns"; import { Button } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui"; import { ConfirmDialog } from "@govoplan/core-webui"; import DataGrid, { type DataGridColumn } from "../../../components/table/DataGrid"; import { Dialog } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui"; import { StatusBadge, TableActionGroup, hasScope, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui"; type TargetType = "user" | "group"; export default function CampaignAccessCard({ settings, auth, campaign, onChanged, onError }: {settings: ApiSettings;auth: AuthInfo;campaign: CampaignListItem;onChanged: () => Promise;onError: (message: string) => void;}) { const [available, setAvailable] = useState(null); const [targets, setTargets] = useState({ users: [], groups: [] }); const [shares, setShares] = useState([]); const [ownerOpen, setOwnerOpen] = useState(false); const [shareOpen, setShareOpen] = useState(false); const [revokeTarget, setRevokeTarget] = useState(null); const [ownerType, setOwnerType] = useState(campaign.owner_group_id ? "group" : "user"); const [ownerId, setOwnerId] = useState(campaign.owner_group_id || campaign.owner_user_id || ""); const [shareType, setShareType] = useState("user"); const [shareTargetId, setShareTargetId] = useState(""); const [sharePermission, setSharePermission] = useState<"read" | "write">("read"); const [accessExplanationOpen, setAccessExplanationOpen] = useState(false); const [accessExplanation, setAccessExplanation] = useState(null); const [accessExplanationLoading, setAccessExplanationLoading] = useState(false); const [busy, setBusy] = useState(false); const currentOwnerType: TargetType = campaign.owner_group_id ? "group" : "user"; const currentOwnerId = campaign.owner_group_id || campaign.owner_user_id || ""; const ownerDirty = ownerOpen && (ownerType !== currentOwnerType || ownerId !== currentOwnerId); const shareDirty = shareOpen && (shareType !== "user" || Boolean(shareTargetId) || sharePermission !== "read"); const canExplainResourceAccess = hasScope(auth, "admin:users:read") || hasScope(auth, "admin:roles:read") || hasScope(auth, "access:membership:read") || hasScope(auth, "access:role:read"); useUnsavedDraftGuard({ dirty: ownerDirty || shareDirty, onSave: saveActiveDialog, onDiscard: () => { closeOwnerDialog(); closeShareDialog(); } }); async function load() { try { const [nextTargets, nextShares] = await Promise.all([ getCampaignShareTargets(settings, campaign.id), getCampaignShares(settings, campaign.id)] ); setTargets(nextTargets); setShares(nextShares.filter((item) => !item.revoked_at)); setAvailable(true); } catch (err) { const message = err instanceof Error ? err.message : String(err); if (message.startsWith("403 ")) setAvailable(false);else onError(message); } } useEffect(() => { setOwnerType(campaign.owner_group_id ? "group" : "user"); setOwnerId(campaign.owner_group_id || campaign.owner_user_id || ""); void load(); }, [campaign.id, campaign.owner_group_id, campaign.owner_user_id, settings.accessToken, settings.apiBaseUrl]); const targetOptions = ownerType === "user" ? targets.users : targets.groups; const shareTargetOptions = shareType === "user" ? targets.users : targets.groups; const targetMap = useMemo(() => new Map([ ...targets.users.map((item) => [`user:${item.id}`, item] as const), ...targets.groups.map((item) => [`group:${item.id}`, item] as const)] ), [targets]); const ownerLabel = campaign.owner_group_id ? targetMap.get(`group:${campaign.owner_group_id}`)?.name || "i18n:govoplan-campaign.group_owner.55630670" : targetMap.get(`user:${campaign.owner_user_id || ""}`)?.name || "i18n:govoplan-campaign.user_owner.86e3faab"; function closeOwnerDialog() { setOwnerOpen(false); setOwnerType(currentOwnerType); setOwnerId(currentOwnerId); } function closeShareDialog() { setShareOpen(false); setShareType("user"); setShareTargetId(""); setSharePermission("read"); } async function saveActiveDialog(): Promise { if (ownerDirty) return saveOwner(); if (shareDirty) return saveShare(); return true; } async function saveOwner(): Promise { if (!ownerId) return false; setBusy(true); try { await updateCampaignOwner(settings, campaign.id, ownerType === "user" ? { owner_user_id: ownerId, owner_group_id: null } : { owner_user_id: null, owner_group_id: ownerId }); setOwnerOpen(false); await onChanged(); await load(); return true; } catch (err) {onError(err instanceof Error ? err.message : String(err));return false;} finally {setBusy(false);} } async function saveShare(): Promise { if (!shareTargetId) return false; setBusy(true); try { await upsertCampaignShare(settings, campaign.id, { target_type: shareType, target_id: shareTargetId, permission: sharePermission }); setShareOpen(false); setShareTargetId(""); await load(); return true; } catch (err) {onError(err instanceof Error ? err.message : String(err));return false;} finally {setBusy(false);} } async function revoke() { if (!revokeTarget) return; setBusy(true); try { await revokeCampaignShare(settings, campaign.id, revokeTarget.id); setRevokeTarget(null); await load(); } catch (err) {onError(err instanceof Error ? err.message : String(err));} finally {setBusy(false);} } async function openAccessExplanation() { if (!auth.user?.id) return; setAccessExplanationOpen(true); setAccessExplanation(null); setAccessExplanationLoading(true); try { setAccessExplanation(await fetchResourceAccessExplanation(settings, { userId: auth.user.id, resourceType: "campaign", resourceId: campaign.id, action: "campaigns:campaign:read", tenantId: (auth.active_tenant ?? auth.tenant)?.id })); } catch (err) { onError(err instanceof Error ? err.message : String(err)); setAccessExplanationOpen(false); } finally { setAccessExplanationLoading(false); } } const columns = useMemo[]>(() => [ { id: "target", header: "i18n:govoplan-campaign.shared_with.6203f449", width: "minmax(220px, 1fr)", minWidth: 190, resizable: true, sticky: "start", sortable: true, filterable: true, value: (row) => targetMap.get(`${row.target_type}:${row.target_id}`)?.name || row.target_id, render: (row) => { const target = targetMap.get(`${row.target_type}:${row.target_id}`); return
{target?.name || row.target_id}
{row.target_type}{target?.secondary ? i18nMessage("i18n:govoplan-campaign.value.48afe802", { value0: target.secondary }) : ""}
; } }, { id: "permission", header: "i18n:govoplan-campaign.access.2f81a22d", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.permission, render: (row) => }, { id: "actions", header: "i18n:govoplan-campaign.actions.c3cd636a", width: 72, resizable: false, sticky: "end", align: "right", render: (row) =>