|
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
|
|
type CampaignShareTargets,
|
|
|
|
|
type ResourceAccessExplanationResponse } from
|
|
|
|
|
"../../../api/campaigns";
|
|
|
|
|
import { Button } from "@govoplan/core-webui";
|
|
|
|
|
import { Button, ResourceAccessExplanation } from "@govoplan/core-webui";
|
|
|
|
|
import { Card } from "@govoplan/core-webui";
|
|
|
|
|
import { ConfirmDialog } from "@govoplan/core-webui";
|
|
|
|
|
import DataGrid, { type DataGridColumn } from "../../../components/table/DataGrid";
|
|
|
|
|
@@ -221,76 +221,11 @@ export default function CampaignAccessCard({
|
|
|
|
|
<FormField label="i18n:govoplan-campaign.access.2f81a22d"><select value={sharePermission} onChange={(event) => setSharePermission(event.target.value as "read" | "write")}><option value="read">i18n:govoplan-campaign.can_view.1b3e4006</option><option value="write">i18n:govoplan-campaign.can_edit_and_operate.77acb15e</option></select></FormField>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
<Dialog open={accessExplanationOpen} className="campaign-access-dialog" title="i18n:govoplan-campaign.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setAccessExplanationOpen(false); setAccessExplanation(null); } }} footer={<Button onClick={() => { setAccessExplanationOpen(false); setAccessExplanation(null); }} disabled={accessExplanationLoading}>i18n:govoplan-campaign.close.bbfa773e</Button>}>
|
|
|
|
|
<ResourceAccessExplanationContent loading={accessExplanationLoading} explanation={accessExplanation} fallbackResourceLabel={campaign.name || campaign.external_id || campaign.id} />
|
|
|
|
|
<Dialog open={accessExplanationOpen} className="campaign-access-dialog" title="i18n:govoplan-core.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setAccessExplanationOpen(false); setAccessExplanation(null); } }} footer={<Button onClick={() => { setAccessExplanationOpen(false); setAccessExplanation(null); }} disabled={accessExplanationLoading}>i18n:govoplan-campaign.close.bbfa773e</Button>}>
|
|
|
|
|
<ResourceAccessExplanation loading={accessExplanationLoading} explanation={accessExplanation} fallbackResourceLabel={campaign.name || campaign.external_id || campaign.id} />
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
<ConfirmDialog open={Boolean(revokeTarget)} title="i18n:govoplan-campaign.remove_campaign_share.2c2d42eb" message="i18n:govoplan-campaign.remove_this_user_s_or_group_s_explicit_access_to.5ff07672" confirmLabel="i18n:govoplan-campaign.remove_share.69c932e1" tone="danger" busy={busy} onCancel={() => setRevokeTarget(null)} onConfirm={() => void revoke()} />
|
|
|
|
|
</>);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ResourceAccessExplanationContent({
|
|
|
|
|
loading,
|
|
|
|
|
explanation,
|
|
|
|
|
fallbackResourceLabel
|
|
|
|
|
}: {loading: boolean;explanation: ResourceAccessExplanationResponse | null;fallbackResourceLabel: string;}) {
|
|
|
|
|
if (loading) return <p className="muted small-note">i18n:govoplan-campaign.loading_access_explanation.04a7c934</p>;
|
|
|
|
|
if (!explanation) return null;
|
|
|
|
|
const userLabel = explanation.user.display_name || explanation.user.email || explanation.user.id;
|
|
|
|
|
const resourceLabel = explanation.provenance.find((item) => item.kind === "resource")?.label || fallbackResourceLabel || explanation.resource_id;
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="form-grid compact responsive-form-grid">
|
|
|
|
|
<div><span className="form-label">i18n:govoplan-campaign.user.9f8a2389</span><p>{userLabel}</p></div>
|
|
|
|
|
<div><span className="form-label">i18n:govoplan-campaign.resource.d1c626a9</span><p>{resourceLabel}</p></div>
|
|
|
|
|
<div><span className="form-label">i18n:govoplan-campaign.action.97c89a4d</span><p><code>{explanation.action}</code></p></div>
|
|
|
|
|
<div><span className="form-label">i18n:govoplan-campaign.evidence.8487d192</span><p>{explanation.provenance.length}</p></div>
|
|
|
|
|
</div>
|
|
|
|
|
{explanation.provenance.length === 0 ?
|
|
|
|
|
<p className="muted small-note">i18n:govoplan-campaign.no_access_evidence_was_returned.84a21e4e</p> :
|
|
|
|
|
<div className="admin-assignment-grid">
|
|
|
|
|
{explanation.provenance.map((item, index) =>
|
|
|
|
|
<div key={`${item.kind}:${item.id ?? index}`}>
|
|
|
|
|
<strong>{provenanceKindLabel(item.kind)}</strong>
|
|
|
|
|
<div className="muted small-note">
|
|
|
|
|
{item.source || "i18n:govoplan-campaign.no_source.6dcf9723"}
|
|
|
|
|
{item.id && <> · <code>{item.id}</code></>}
|
|
|
|
|
</div>
|
|
|
|
|
{item.label && <p>{item.label}</p>}
|
|
|
|
|
{Object.keys(item.details ?? {}).length > 0 && <p className="muted small-note">{formatProvenanceDetails(item.details ?? {})}</p>}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function provenanceKindLabel(kind: string): string {
|
|
|
|
|
switch (kind) {
|
|
|
|
|
case "resource":
|
|
|
|
|
return "i18n:govoplan-campaign.resource.d1c626a9";
|
|
|
|
|
case "owner":
|
|
|
|
|
return "i18n:govoplan-campaign.owner.89ff3122";
|
|
|
|
|
case "share":
|
|
|
|
|
return "i18n:govoplan-campaign.share.09ca55ca";
|
|
|
|
|
case "policy":
|
|
|
|
|
return "i18n:govoplan-campaign.policy.0b779a05";
|
|
|
|
|
case "role":
|
|
|
|
|
return "i18n:govoplan-campaign.role.b5b4a5a2";
|
|
|
|
|
case "right":
|
|
|
|
|
return "i18n:govoplan-campaign.permission.2f81a22d";
|
|
|
|
|
default:
|
|
|
|
|
return kind;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatProvenanceDetails(details: Record<string, unknown>): string {
|
|
|
|
|
return Object.entries(details).map(([key, value]) => `${key}: ${formatProvenanceValue(value)}`).join("; ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatProvenanceValue(value: unknown): string {
|
|
|
|
|
if (value === null || value === undefined) return "i18n:govoplan-campaign.none.6eef6648";
|
|
|
|
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
|
|
|
|
|
return JSON.stringify(value);
|
|
|
|
|
}
|
|
|
|
|
|