feat: add exact retention help contexts
This commit is contained in:
@@ -8,6 +8,7 @@ const read = (path) => readFileSync(resolve(webuiRoot, path), "utf8");
|
||||
|
||||
const settings = read("src/features/settings/SettingsPage.tsx");
|
||||
const retention = read("src/features/privacy/RetentionPolicyManagement.tsx");
|
||||
const confirmDialog = read("src/components/ConfirmDialog.tsx");
|
||||
const credentials = read("src/components/CredentialEnvelopeManager.tsx");
|
||||
const iconRail = read("src/layout/IconRail.tsx");
|
||||
const moduleLoadBoundary = read("src/components/ModuleLoadBoundary.tsx");
|
||||
@@ -24,8 +25,14 @@ assert.match(settings, /There are no unsaved interface changes\./, "preference s
|
||||
|
||||
assert.match(retention, /<ActionBlockerHint/, "retention renders the shared actionable blocker");
|
||||
assert.match(retention, /contextId: "privacy\.retention"/, "retention exposes stable admin documentation");
|
||||
assert.match(retention, /policy\.retention\.field\.store-raw-campaign-json/, "retention storage controls expose exact help contexts");
|
||||
assert.match(retention, /policy\.retention\.field\.audit-detail-level/, "retention audit controls expose exact help contexts");
|
||||
assert.match(retention, /policy\.retention\.action\.save/, "retention persistence exposes exact help contexts");
|
||||
assert.match(retention, /helpModuleId="policy"/, "embedded retention controls retain Policy as their documentation owner");
|
||||
assert.match(retention, /locked by platform configuration/, "retention explains platform locks");
|
||||
assert.doesNotMatch(retention, /<textarea/, "retention does not use raw text or JSON as its primary editor");
|
||||
assert.match(confirmDialog, /PlatformInterfaceIdentityProps/, "confirm dialogs accept stable interface help identities");
|
||||
assert.match(confirmDialog, /helpContextId=\{helpContextId\}/, "confirm dialogs propagate their explicit help context");
|
||||
|
||||
assert.match(credentials, /<ActionBlockerHint/, "credentials render the shared actionable blocker");
|
||||
assert.match(credentials, /contextId: "access\.credentials"/, "credentials expose stable admin documentation");
|
||||
@@ -50,6 +57,7 @@ assert.match(helpMenu, /helpContextForTarget\(routeHelpContext, event\.target, m
|
||||
assert.match(helpContext, /function moduleRouteContext/, "context help covers contributed module routes");
|
||||
assert.match(helpContext, /function sectionContext/, "context help covers contributed administration and settings sections");
|
||||
assert.match(helpContext, /data-help-context-id/, "context help honors explicit control metadata");
|
||||
assert.match(helpContext, /explicit\.dataset\.helpModuleId \|\| base\.moduleId/, "context help honors an explicit documentation owner");
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.app-main \{[\s\S]*grid-template-rows: 104px 51px minmax\(0, 1fr\);/, "the narrow shell reserves two non-overlapping titlebar rows");
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.titlebar-context-selectors \{[\s\S]*overflow-x: auto;/, "narrow context selectors remain reachable without covering titlebar actions");
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.account-pill span \{[\s\S]*display: none;/, "narrow account controls retain the icon while removing collision-prone text");
|
||||
|
||||
@@ -7,12 +7,13 @@ export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & PlatformInte
|
||||
disabledReason?: ReactNode;
|
||||
};
|
||||
|
||||
export default function Button({ variant = "secondary", className = "", disabledReason, disabled, interfaceId, helpContextId, helpTopicId, children, ...props }: ButtonProps) {
|
||||
export default function Button({ variant = "secondary", className = "", disabledReason, disabled, interfaceId, helpContextId, helpModuleId, helpTopicId, children, ...props }: ButtonProps) {
|
||||
const button = (
|
||||
<button
|
||||
data-help-scope="action"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={typeof children === "string" ? children : undefined}
|
||||
className={`btn btn-${variant} ${className}`}
|
||||
|
||||
@@ -39,7 +39,7 @@ function writeCollapseState(storageKey: string | null, collapsed: boolean): void
|
||||
// localStorage may be unavailable in private or restricted contexts.
|
||||
}}
|
||||
|
||||
export default function Card({ title, children, actions, collapsible = false, collapseKey, persistCollapse = true, interfaceId, helpContextId, helpTopicId }: CardProps) {
|
||||
export default function Card({ title, children, actions, collapsible = false, collapseKey, persistCollapse = true, interfaceId, helpContextId, helpModuleId, helpTopicId }: CardProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
const storageKey = resolveCollapseStorageKey(collapsible, persistCollapse, collapseKey, title);
|
||||
const [collapseState, setCollapseState] = useState(() => ({ storageKey, collapsed: readCollapseState(storageKey) }));
|
||||
@@ -65,6 +65,7 @@ export default function Card({ title, children, actions, collapsible = false, co
|
||||
data-help-scope="interface"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={typeof title === "string" ? title : undefined}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import Button from "./Button";
|
||||
import Dialog from "./Dialog";
|
||||
import { usePlatformLanguage } from "../i18n/LanguageContext";
|
||||
import type { PlatformInterfaceIdentityProps } from "../types";
|
||||
|
||||
export type ConfirmDialogTone = "default" | "danger";
|
||||
|
||||
export type ConfirmDialogProps = {
|
||||
export type ConfirmDialogProps = PlatformInterfaceIdentityProps & {
|
||||
open: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
@@ -24,6 +25,10 @@ export default function ConfirmDialog({
|
||||
cancelLabel = "i18n:govoplan-core.cancel.77dfd213",
|
||||
tone = "default",
|
||||
busy = false,
|
||||
interfaceId,
|
||||
helpContextId,
|
||||
helpModuleId,
|
||||
helpTopicId,
|
||||
onConfirm,
|
||||
onCancel
|
||||
}: ConfirmDialogProps) {
|
||||
@@ -38,11 +43,15 @@ export default function ConfirmDialog({
|
||||
closeOnBackdrop={!busy}
|
||||
closeDisabled={busy}
|
||||
showCloseButton
|
||||
interfaceId={interfaceId}
|
||||
helpContextId={helpContextId}
|
||||
helpModuleId={helpModuleId}
|
||||
helpTopicId={helpTopicId}
|
||||
onClose={onCancel}
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onCancel} disabled={busy}>{translateText(cancelLabel)}</Button>
|
||||
<Button variant={tone === "danger" ? "danger" : "primary"} onClick={onConfirm} disabled={busy}>{busy ? translateText("i18n:govoplan-core.working.13b7bfca") : translateText(confirmLabel)}</Button>
|
||||
<Button helpContextId={helpContextId} helpModuleId={helpModuleId} helpTopicId={helpTopicId} variant={tone === "danger" ? "danger" : "primary"} onClick={onConfirm} disabled={busy}>{busy ? translateText("i18n:govoplan-core.working.13b7bfca") : translateText(confirmLabel)}</Button>
|
||||
</>
|
||||
}>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function combineDateTime(date: string, time: string): string {
|
||||
return `${date || dateString(new Date())}T${time || "00:00"}`;
|
||||
}
|
||||
|
||||
export function DateField({ value, onChange, min, max, disabled, className = "", placeholder = "i18n:govoplan-core.yyyy_mm_dd.d3f8f7b8", interfaceId, helpContextId, helpTopicId, ...props }: BaseProps) {
|
||||
export function DateField({ value, onChange, min, max, disabled, className = "", placeholder = "i18n:govoplan-core.yyyy_mm_dd.d3f8f7b8", interfaceId, helpContextId, helpModuleId, helpTopicId, ...props }: BaseProps) {
|
||||
const selectedDate = parseDate(value);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visibleMonth, setVisibleMonth] = useState<Date>(() => selectedDate ?? new Date());
|
||||
@@ -100,6 +100,7 @@ export function DateField({ value, onChange, min, max, disabled, className = "",
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={props["aria-label"] ?? placeholder}
|
||||
>
|
||||
@@ -154,7 +155,7 @@ export function DateField({ value, onChange, min, max, disabled, className = "",
|
||||
|
||||
}
|
||||
|
||||
export function TimeField({ value, onChange, min, max, className = "", placeholder = "i18n:govoplan-core.hh_mm.a4c7ee9b", interfaceId, helpContextId, helpTopicId, ...props }: BaseProps) {
|
||||
export function TimeField({ value, onChange, min, max, className = "", placeholder = "i18n:govoplan-core.hh_mm.a4c7ee9b", interfaceId, helpContextId, helpModuleId, helpTopicId, ...props }: BaseProps) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
useEffect(() => {
|
||||
const input = inputRef.current;
|
||||
@@ -172,6 +173,7 @@ export function TimeField({ value, onChange, min, max, className = "", placehold
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={props["aria-label"] ?? placeholder}
|
||||
>
|
||||
@@ -190,7 +192,7 @@ export function TimeField({ value, onChange, min, max, className = "", placehold
|
||||
|
||||
}
|
||||
|
||||
export function DateTimeField({ value, onChange, min, max, disabled, className = "", interfaceId, helpContextId, helpTopicId, ...props }: BaseProps) {
|
||||
export function DateTimeField({ value, onChange, min, max, disabled, className = "", interfaceId, helpContextId, helpModuleId, helpTopicId, ...props }: BaseProps) {
|
||||
const parts = datePartsFromDateTime(value);
|
||||
const minParts = datePartsFromDateTime(min || "");
|
||||
const maxParts = datePartsFromDateTime(max || "");
|
||||
@@ -209,6 +211,7 @@ export function DateTimeField({ value, onChange, min, max, disabled, className =
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={props["aria-label"]}
|
||||
>
|
||||
|
||||
@@ -60,6 +60,7 @@ export default function Dialog({
|
||||
backdropStyle,
|
||||
interfaceId,
|
||||
helpContextId,
|
||||
helpModuleId,
|
||||
helpTopicId
|
||||
}: DialogProps) {
|
||||
const titleId = useId();
|
||||
@@ -141,6 +142,7 @@ export default function Dialog({
|
||||
data-help-scope="dialog"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={typeof title === "string" ? title : undefined}
|
||||
aria-labelledby={titleId}
|
||||
|
||||
@@ -12,7 +12,7 @@ type FormFieldProps = PlatformInterfaceIdentityProps & {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function FormField({ label, help, documentation, children, interfaceId, helpContextId, helpTopicId }: FormFieldProps) {
|
||||
export default function FormField({ label, help, documentation, children, interfaceId, helpContextId, helpModuleId, helpTopicId }: FormFieldProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
const renderedLabel = typeof label === "string" ? translateText(label) : label;
|
||||
return (
|
||||
@@ -21,6 +21,7 @@ export default function FormField({ label, help, documentation, children, interf
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId ?? documentation?.contextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId ?? documentation?.topicId}
|
||||
data-help-documentation-type={documentation?.documentationType}
|
||||
data-help-key={typeof label === "string" ? label : undefined}
|
||||
|
||||
@@ -99,6 +99,7 @@ export default function SearchableSelect({
|
||||
className = "",
|
||||
interfaceId,
|
||||
helpContextId,
|
||||
helpModuleId,
|
||||
helpTopicId
|
||||
}: SearchableSelectProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
@@ -301,6 +302,7 @@ export default function SearchableSelect({
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={ariaLabel}
|
||||
onBlur={closeOnFocusLeave}
|
||||
|
||||
@@ -14,7 +14,7 @@ type ToggleSwitchProps = PlatformInterfaceIdentityProps & {
|
||||
help?: ReactNode;
|
||||
};
|
||||
|
||||
export default function ToggleSwitch({ label, activeLabel, inactiveLabel, checked, onChange, disabled = false, help, interfaceId, helpContextId, helpTopicId }: ToggleSwitchProps) {
|
||||
export default function ToggleSwitch({ label, activeLabel, inactiveLabel, checked, onChange, disabled = false, help, interfaceId, helpContextId, helpModuleId, helpTopicId }: ToggleSwitchProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
const hasStateLabels = activeLabel !== undefined || inactiveLabel !== undefined;
|
||||
const renderedLabel = typeof label === "string" ? translateText(label) : label;
|
||||
@@ -27,6 +27,7 @@ export default function ToggleSwitch({ label, activeLabel, inactiveLabel, checke
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={typeof label === "string" ? label : undefined}
|
||||
>
|
||||
|
||||
@@ -29,6 +29,7 @@ export default function AdminPageLayout({
|
||||
className = "",
|
||||
interfaceId,
|
||||
helpContextId,
|
||||
helpModuleId,
|
||||
helpTopicId
|
||||
}: Props) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
@@ -38,6 +39,7 @@ export default function AdminPageLayout({
|
||||
data-help-scope="page"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-documentation-type="admin"
|
||||
data-help-key={title}
|
||||
|
||||
@@ -47,6 +47,7 @@ export default function EmailAddressInput({
|
||||
showAddButton,
|
||||
interfaceId,
|
||||
helpContextId,
|
||||
helpModuleId,
|
||||
helpTopicId
|
||||
}: EmailAddressInputProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
@@ -208,6 +209,7 @@ export default function EmailAddressInput({
|
||||
data-help-scope="field"
|
||||
data-interface-id={interfaceId}
|
||||
data-help-context-id={helpContextId}
|
||||
data-help-module-id={helpModuleId}
|
||||
data-help-topic-id={helpTopicId}
|
||||
data-help-key={emailPlaceholder}
|
||||
>
|
||||
|
||||
@@ -67,6 +67,7 @@ type DayKey =
|
||||
type FieldDefinition = {
|
||||
key: PrivacyRetentionPolicyFieldKey;
|
||||
label: string;
|
||||
helpContextId: string;
|
||||
kind: "raw-json" | "days" | "audit";
|
||||
systemOnly?: boolean;
|
||||
};
|
||||
@@ -101,13 +102,13 @@ const defaultPrivacyPolicy: PrivacyRetentionPolicy = {
|
||||
};
|
||||
|
||||
const fieldDefinitions: FieldDefinition[] = [
|
||||
{ key: "store_raw_campaign_json", label: "i18n:govoplan-core.raw_campaign_json.4ca2265b", kind: "raw-json" },
|
||||
{ key: "raw_campaign_json_retention_days", label: "i18n:govoplan-core.raw_campaign_json_days.48805a59", kind: "days" },
|
||||
{ key: "generated_eml_retention_days", label: "i18n:govoplan-core.generated_eml_days.bcb273d9", kind: "days" },
|
||||
{ key: "stored_report_detail_retention_days", label: "i18n:govoplan-core.stored_report_detail_days.296f6dab", kind: "days" },
|
||||
{ key: "mock_mailbox_retention_days", label: "i18n:govoplan-core.mock_mailbox_days.a0cf3209", kind: "days", systemOnly: true },
|
||||
{ key: "audit_detail_retention_days", label: "i18n:govoplan-core.audit_detail_days.21c84dd1", kind: "days" },
|
||||
{ key: "audit_detail_level", label: "i18n:govoplan-core.audit_detail_level.b0565260", kind: "audit" }];
|
||||
{ key: "store_raw_campaign_json", label: "i18n:govoplan-core.raw_campaign_json.4ca2265b", helpContextId: "policy.retention.field.store-raw-campaign-json", kind: "raw-json" },
|
||||
{ key: "raw_campaign_json_retention_days", label: "i18n:govoplan-core.raw_campaign_json_days.48805a59", helpContextId: "policy.retention.field.raw-campaign-json-retention-days", kind: "days" },
|
||||
{ key: "generated_eml_retention_days", label: "i18n:govoplan-core.generated_eml_days.bcb273d9", helpContextId: "policy.retention.field.generated-eml-retention-days", kind: "days" },
|
||||
{ key: "stored_report_detail_retention_days", label: "i18n:govoplan-core.stored_report_detail_days.296f6dab", helpContextId: "policy.retention.field.stored-report-detail-retention-days", kind: "days" },
|
||||
{ key: "mock_mailbox_retention_days", label: "i18n:govoplan-core.mock_mailbox_days.a0cf3209", helpContextId: "policy.retention.field.mock-mailbox-retention-days", kind: "days", systemOnly: true },
|
||||
{ key: "audit_detail_retention_days", label: "i18n:govoplan-core.audit_detail_days.21c84dd1", helpContextId: "policy.retention.field.audit-detail-retention-days", kind: "days" },
|
||||
{ key: "audit_detail_level", label: "i18n:govoplan-core.audit_detail_level.b0565260", helpContextId: "policy.retention.field.audit-detail-level", kind: "audit" }];
|
||||
|
||||
|
||||
export function RetentionPolicyScopeManager({
|
||||
@@ -148,11 +149,13 @@ export function RetentionPolicyScopeManager({
|
||||
{targetSelectionRequired &&
|
||||
<Card
|
||||
title={i18nMessage("i18n:govoplan-core.value_scope", { value0: targetLabel })}
|
||||
helpContextId="policy.retention.target"
|
||||
helpModuleId="policy"
|
||||
actions={<DocumentationHelpLink reference={RETENTION_DOCUMENTATION} label="i18n:govoplan-core.open_admin_documentation.6adbdae3" />}
|
||||
>
|
||||
<div className="retention-policy-target-row">
|
||||
<FormField label={targetLabel}>
|
||||
<select value={selectedTargetId} disabled={!hasSelectableTarget} onChange={(event) => setSelectedTargetId(event.target.value)}>
|
||||
<FormField label={targetLabel} helpContextId="policy.retention.target" helpModuleId="policy">
|
||||
<select data-help-context-id="policy.retention.target" data-help-module-id="policy" value={selectedTargetId} disabled={!hasSelectableTarget} onChange={(event) => setSelectedTargetId(event.target.value)}>
|
||||
{!hasSelectableTarget && <option value="">{targetEmptyText}</option>}
|
||||
{targetOptions.map((option) => <option key={option.id} value={option.id}>{option.secondary ? i18nMessage("i18n:govoplan-core.value_value.c189e8bc", { value0: option.label, value1: option.secondary }) : option.label}</option>)}
|
||||
</select>
|
||||
@@ -342,11 +345,13 @@ export function RetentionPolicyEditor({
|
||||
return (
|
||||
<Card
|
||||
title={title}
|
||||
helpContextId="policy.retention"
|
||||
helpModuleId="policy"
|
||||
actions={
|
||||
<div className="button-row compact-actions">
|
||||
<DocumentationHelpLink reference={RETENTION_DOCUMENTATION} label="i18n:govoplan-core.open_admin_documentation.6adbdae3" />
|
||||
<Button onClick={() => void loadPolicy()} disabled={Boolean(reloadDisabledReason)} disabledReason={reloadDisabledReason}>{loading ? "i18n:govoplan-core.loading.33ce4174" : "i18n:govoplan-core.reload.cce71553"}</Button>
|
||||
<Button variant="primary" onClick={() => void savePolicy()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-core.saving.56a2285c" : "i18n:govoplan-core.save_policy.77d67ce3"}</Button>
|
||||
<Button helpContextId="policy.retention.action.reload" helpModuleId="policy" onClick={() => void loadPolicy()} disabled={Boolean(reloadDisabledReason)} disabledReason={reloadDisabledReason}>{loading ? "i18n:govoplan-core.loading.33ce4174" : "i18n:govoplan-core.reload.cce71553"}</Button>
|
||||
<Button helpContextId="policy.retention.action.save" helpModuleId="policy" variant="primary" onClick={() => void savePolicy()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-core.saving.56a2285c" : "i18n:govoplan-core.save_policy.77d67ce3"}</Button>
|
||||
</div>
|
||||
}>
|
||||
|
||||
@@ -401,6 +406,8 @@ export function RetentionPolicyEditor({
|
||||
<ToggleSwitch
|
||||
checked={localAllowsLower(field.key)}
|
||||
disabled={disabled || fieldLocked}
|
||||
helpContextId="policy.retention.field.allow-lower-level-limits"
|
||||
helpModuleId="policy"
|
||||
onChange={(checked) => setAllowLowerLevelLimit(field.key, checked)}
|
||||
label="i18n:govoplan-core.allow_override.ffa6e9a0" /> :
|
||||
undefined} />);
|
||||
@@ -519,18 +526,19 @@ parentPolicy: PrivacyRetentionPolicy | null)
|
||||
{
|
||||
if (field.kind === "raw-json") {
|
||||
if (isSystem) {
|
||||
return <RawJsonSystemSelect value={fullPolicy.store_raw_campaign_json ? "store" : "disabled"} disabled={disabled} onChange={setRawJson} />;
|
||||
return <RawJsonSystemSelect value={fullPolicy.store_raw_campaign_json ? "store" : "disabled"} disabled={disabled} helpContextId={field.helpContextId} onChange={setRawJson} />;
|
||||
}
|
||||
return <RawJsonScopedSelect value={rawJsonValue(patchPolicy.store_raw_campaign_json)} parentStoresRawJson={parentPolicy?.store_raw_campaign_json ?? true} disabled={disabled} onChange={setRawJson} />;
|
||||
return <RawJsonScopedSelect value={rawJsonValue(patchPolicy.store_raw_campaign_json)} parentStoresRawJson={parentPolicy?.store_raw_campaign_json ?? true} disabled={disabled} helpContextId={field.helpContextId} onChange={setRawJson} />;
|
||||
}
|
||||
if (field.kind === "audit") {
|
||||
return <AuditDetailSelect value={isSystem ? fullPolicy.audit_detail_level : auditDetailValue(patchPolicy.audit_detail_level)} includeInherit={!isSystem} parentValue={isSystem ? null : parentPolicy?.audit_detail_level ?? null} disabled={disabled} onChange={setAuditDetail} />;
|
||||
return <AuditDetailSelect value={isSystem ? fullPolicy.audit_detail_level : auditDetailValue(patchPolicy.audit_detail_level)} includeInherit={!isSystem} parentValue={isSystem ? null : parentPolicy?.audit_detail_level ?? null} disabled={disabled} helpContextId={field.helpContextId} onChange={setAuditDetail} />;
|
||||
}
|
||||
const parentDayLimit = !isSystem && parentPolicy ? parentPolicy[field.key as DayKey] : null;
|
||||
return (
|
||||
<RetentionDaysField
|
||||
value={isSystem ? fullPolicy[field.key as DayKey] : patchPolicy[field.key as DayKey]}
|
||||
disabled={disabled}
|
||||
helpContextId={field.helpContextId}
|
||||
placeholder={isSystem ? "i18n:govoplan-core.unlimited.b8bef37b" : "i18n:govoplan-core.inherit.18f99833"}
|
||||
max={parentDayLimit}
|
||||
onChange={(value) => setRetentionDays(field.key as DayKey, value)} />);
|
||||
@@ -538,18 +546,18 @@ parentPolicy: PrivacyRetentionPolicy | null)
|
||||
|
||||
}
|
||||
|
||||
function RawJsonSystemSelect({ value, disabled, onChange }: {value: "store" | "disabled";disabled: boolean;onChange: (value: "store" | "disabled") => void;}) {
|
||||
function RawJsonSystemSelect({ value, disabled, helpContextId, onChange }: {value: "store" | "disabled";disabled: boolean;helpContextId: string;onChange: (value: "store" | "disabled") => void;}) {
|
||||
return (
|
||||
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as "store" | "disabled")}>
|
||||
<select data-help-scope="field" data-help-context-id={helpContextId} data-help-module-id="policy" value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as "store" | "disabled")}>
|
||||
<option value="store">i18n:govoplan-core.store.0d8a7046</option>
|
||||
<option value="disabled">i18n:govoplan-core.disable_storage.07c44d8b</option>
|
||||
</select>);
|
||||
|
||||
}
|
||||
|
||||
function RawJsonScopedSelect({ value, parentStoresRawJson, disabled, onChange }: {value: RawJsonValue;parentStoresRawJson: boolean;disabled: boolean;onChange: (value: RawJsonValue) => void;}) {
|
||||
function RawJsonScopedSelect({ value, parentStoresRawJson, disabled, helpContextId, onChange }: {value: RawJsonValue;parentStoresRawJson: boolean;disabled: boolean;helpContextId: string;onChange: (value: RawJsonValue) => void;}) {
|
||||
return (
|
||||
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as RawJsonValue)}>
|
||||
<select data-help-scope="field" data-help-context-id={helpContextId} data-help-module-id="policy" value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as RawJsonValue)}>
|
||||
<option value="inherit">i18n:govoplan-core.inherit.18f99833</option>
|
||||
<option value="keep" disabled={!parentStoresRawJson}>i18n:govoplan-core.store_if_parent_allows.36dcb07b</option>
|
||||
<option value="disable">i18n:govoplan-core.disable_storage.07c44d8b</option>
|
||||
@@ -557,7 +565,7 @@ function RawJsonScopedSelect({ value, parentStoresRawJson, disabled, onChange }:
|
||||
|
||||
}
|
||||
|
||||
function RetentionDaysField({ value, disabled, placeholder, max, onChange }: {value?: number | null;disabled: boolean;placeholder: string;max?: number | null;onChange: (value: string) => void;}) {
|
||||
function RetentionDaysField({ value, disabled, helpContextId, placeholder, max, onChange }: {value?: number | null;disabled: boolean;helpContextId: string;placeholder: string;max?: number | null;onChange: (value: string) => void;}) {
|
||||
function handleChange(nextValue: string) {
|
||||
const trimmed = nextValue.trim();
|
||||
if (trimmed === "" || max === null || max === undefined) {
|
||||
@@ -568,16 +576,16 @@ function RetentionDaysField({ value, disabled, placeholder, max, onChange }: {va
|
||||
onChange(Number.isFinite(parsed) && parsed > max ? String(max) : nextValue);
|
||||
}
|
||||
|
||||
return <input type="number" min={0} max={max ?? undefined} value={value ?? ""} disabled={disabled} placeholder={placeholder} onChange={(event) => handleChange(event.target.value)} />;
|
||||
return <input data-help-scope="field" data-help-context-id={helpContextId} data-help-module-id="policy" type="number" min={0} max={max ?? undefined} value={value ?? ""} disabled={disabled} placeholder={placeholder} onChange={(event) => handleChange(event.target.value)} />;
|
||||
}
|
||||
|
||||
function AuditDetailSelect({ value, includeInherit, parentValue, disabled, onChange }: {value: AuditDetailValue;includeInherit: boolean;parentValue?: PrivacyRetentionPolicy["audit_detail_level"] | null;disabled: boolean;onChange: (value: AuditDetailValue) => void;}) {
|
||||
function AuditDetailSelect({ value, includeInherit, parentValue, disabled, helpContextId, onChange }: {value: AuditDetailValue;includeInherit: boolean;parentValue?: PrivacyRetentionPolicy["audit_detail_level"] | null;disabled: boolean;helpContextId: string;onChange: (value: AuditDetailValue) => void;}) {
|
||||
function optionDisabled(option: PrivacyRetentionPolicy["audit_detail_level"]): boolean {
|
||||
return privacyRetentionAuditDetailOptionDisabled(option, parentValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as AuditDetailValue)}>
|
||||
<select data-help-scope="field" data-help-context-id={helpContextId} data-help-module-id="policy" value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as AuditDetailValue)}>
|
||||
{includeInherit && <option value="inherit">i18n:govoplan-core.inherit.18f99833</option>}
|
||||
<option value="full" disabled={optionDisabled("full")}>i18n:govoplan-core.full.10b28a8c</option>
|
||||
<option value="redacted" disabled={optionDisabled("redacted")}>i18n:govoplan-core.redacted.8780785a</option>
|
||||
|
||||
@@ -426,6 +426,8 @@ export type PlatformInterfaceIdentityProps = {
|
||||
interfaceId?: string;
|
||||
/** Stable contextual-help identifier associated with the control. */
|
||||
helpContextId?: string;
|
||||
/** Documentation-owning module when it differs from the containing surface. */
|
||||
helpModuleId?: string;
|
||||
/** Optional stable documentation/help topic associated with the control. */
|
||||
helpTopicId?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user