"use client"; /** * Final-review chip modal: **Core values** and **method** facets share the * kebab → **Customize** / **Duplicate** (values only when under the cap) / * **Remove** pattern from the create-card facet modals (`Create` + * {@link buildCustomRuleModalKebabMenu}). Core values use a single Customize * header field for the value name; method chips use the full policy title + * description pair. * * Template-only chips without an `overrideKey` never mount this component; they * use {@link TemplateChipDetailModal} from the parent. * * @see CommunicationMethodsScreen — mental model for method modals. */ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Create from "../../../components/modals/Create"; import ContentLockup from "../../../components/type/ContentLockup"; import { useMessages, useTranslation } from "../../../contexts/MessagesContext"; import { CommunicationMethodEditFields, ConflictManagementEditFields, CoreValueEditFields, DecisionApproachEditFields, MembershipMethodEditFields, } from "./methodEditFields"; import CustomMethodCardModalBody from "./CustomMethodCardModalBody"; import MethodCardCustomizeModalHeader from "./MethodCardCustomizeModalHeader"; import { buildCustomRuleModalKebabMenu } from "./customRuleModalKebabMenu"; import { useDiscardCustomizeConfirm } from "../hooks/useDiscardCustomizeConfirm"; import { communicationPresetFor, conflictManagementPresetFor, coreValuePresetFor, decisionApproachPresetFor, membershipPresetFor, } from "../../../../lib/create/finalReviewChipPresets"; import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId"; import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks"; import { CUSTOM_RULE_FACET_BY_GROUP, type TemplateFacetGroupKey, } from "../../../../lib/create/customRuleFacets"; import type { MethodFacetGroupKey } from "../../../../lib/create/removeMethodCardFromFacetSelection"; import { removeMethodCardFromFacetSelection } from "../../../../lib/create/removeMethodCardFromFacetSelection"; import { mergePresetMethodsWithCustom } from "../../../../lib/create/mergePresetMethodsWithCustom"; import { moveFacetSelectionIdToFront } from "../../../../lib/create/methodCardSelectionOrder"; import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody"; import { duplicateCoreValueChipInDraft, removeCoreValueChipFromDraft, } from "../../../../lib/create/coreValueChipFacet"; import { captureMethodCardCustomizeSnapshot, isMethodCardCustomizeSessionDirty, type MethodCardCustomizeSnapshot, type MethodCardHeaderDraft, } from "../../../../lib/create/methodCardCustomizeSession"; import type { CommunicationMethodDetailEntry, ConflictManagementDetailEntry, CoreValueDetailEntry, CreateFlowState, DecisionApproachDetailEntry, MembershipMethodDetailEntry, } from "../types"; export type FinalReviewChipEditTarget = { /** Stable key for override lookup: preset id (methods) or chip id (core values). */ overrideKey: string; /** Category group that decides which field set to render. */ groupKey: TemplateFacetGroupKey; /** Display label shown at the top of the modal (localized chip label). */ chipLabel: string; }; export type FinalReviewChipEditPatch = | { groupKey: "coreValues"; overrideKey: string; value: CoreValueDetailEntry; /** When set, updates the display label for this chip id in `coreValuesChipsSnapshot`. */ chipLabel?: string; } | { groupKey: "communication"; overrideKey: string; value: CommunicationMethodDetailEntry; customMethodCardFieldBlocks?: CustomMethodCardFieldBlock[]; methodCardMeta?: { label: string; supportText: string }; } | { groupKey: "membership"; overrideKey: string; value: MembershipMethodDetailEntry; customMethodCardFieldBlocks?: CustomMethodCardFieldBlock[]; methodCardMeta?: { label: string; supportText: string }; } | { groupKey: "decisionApproaches"; overrideKey: string; value: DecisionApproachDetailEntry; customMethodCardFieldBlocks?: CustomMethodCardFieldBlock[]; methodCardMeta?: { label: string; supportText: string }; } | { groupKey: "conflictManagement"; overrideKey: string; value: ConflictManagementDetailEntry; customMethodCardFieldBlocks?: CustomMethodCardFieldBlock[]; methodCardMeta?: { label: string; supportText: string }; }; export interface FinalReviewChipEditModalProps { isOpen: boolean; onClose: () => void; target: FinalReviewChipEditTarget | null; state: CreateFlowState; onSave: (_patch: FinalReviewChipEditPatch) => void; replaceState: (_updater: (prev: CreateFlowState) => CreateFlowState) => void; onInteract?: () => void; /** After core-value **Duplicate**, re-point the open modal at the new chip id. */ onEditTargetChange?: (_next: FinalReviewChipEditTarget) => void; } type Draft = | { groupKey: "coreValues"; value: CoreValueDetailEntry } | { groupKey: "communication"; value: CommunicationMethodDetailEntry } | { groupKey: "membership"; value: MembershipMethodDetailEntry } | { groupKey: "decisionApproaches"; value: DecisionApproachDetailEntry } | { groupKey: "conflictManagement"; value: ConflictManagementDetailEntry }; type MethodDetailDraft = | CommunicationMethodDetailEntry | MembershipMethodDetailEntry | DecisionApproachDetailEntry | ConflictManagementDetailEntry; function methodDetailDraftForCustomizeSession( draft: Draft | null, ): MethodDetailDraft | null { if (!draft || draft.groupKey === "coreValues") return null; return draft.value; } function isMethodFacetGroup( k: TemplateFacetGroupKey, ): k is MethodFacetGroupKey { return k !== "coreValues"; } export function FinalReviewChipEditModal({ isOpen, onClose, target, state, onSave, replaceState, onInteract, onEditTargetChange, }: FinalReviewChipEditModalProps) { const m = useMessages(); const cr = m.create.customRule; const tCv = cr.coreValues; const tComm = cr.communication; const tMem = cr.membership; const tDa = cr.decisionApproaches; const tCm = cr.conflictManagement; const modalKebabMenu = cr.modalKebabMenu; const tModal = useTranslation( "create.reviewAndComplete.finalReview.chipEditModal", ); const { confirmDiscard, confirmDirtyCustomizeCancel, confirmDialog } = useDiscardCustomizeConfirm(); const [draft, setDraft] = useState(null); const [modalEditUnlocked, setModalEditUnlocked] = useState(false); const [draftFieldBlocks, setDraftFieldBlocks] = useState< CustomMethodCardFieldBlock[] | null >(null); const [customizeHeaderDraft, setCustomizeHeaderDraft] = useState(null); const initialSnapshotRef = useRef(""); const seededTargetRef = useRef(null); const customizeSnapshotRef = useRef< | MethodCardCustomizeSnapshot< | CommunicationMethodDetailEntry | MembershipMethodDetailEntry | DecisionApproachDetailEntry | ConflictManagementDetailEntry > | null >(null); const coreCustomizeSnapshotRef = useRef | null>(null); const pendingEphemeralCoreDuplicateRef = useRef(null); const methodById = useMemo(() => { if (!target || !isMethodFacetGroup(target.groupKey)) { return new Map(); } const facet = CUSTOM_RULE_FACET_BY_GROUP.get(target.groupKey)!; const selectedIds = facet.selectionIds(state); switch (target.groupKey) { case "communication": return new Map( mergePresetMethodsWithCustom( tComm.methods, selectedIds, state.customMethodCardMetaById, ).map((row) => [row.id, row]), ); case "membership": return new Map( mergePresetMethodsWithCustom( tMem.methods, selectedIds, state.customMethodCardMetaById, ).map((row) => [row.id, row]), ); case "decisionApproaches": return new Map( mergePresetMethodsWithCustom( tDa.methods, selectedIds, state.customMethodCardMetaById, ).map((row) => [row.id, row]), ); case "conflictManagement": return new Map( mergePresetMethodsWithCustom( tCm.methods, selectedIds, state.customMethodCardMetaById, ).map((row) => [row.id, row]), ); } }, [ target, state.customMethodCardMetaById, state.selectedCommunicationMethodIds, state.selectedMembershipMethodIds, state.selectedDecisionApproachIds, state.selectedConflictManagementIds, tComm.methods, tMem.methods, tDa.methods, tCm.methods, ]); const selectionIdsForTarget = useMemo(() => { if (!target || !isMethodFacetGroup(target.groupKey)) return []; return [...CUSTOM_RULE_FACET_BY_GROUP.get(target.groupKey)!.selectionIds(state)]; }, [ target, state.selectedCommunicationMethodIds, state.selectedMembershipMethodIds, state.selectedDecisionApproachIds, state.selectedConflictManagementIds, ]); const isChipInSelection = target && isMethodFacetGroup(target.groupKey) ? selectionIdsForTarget.includes(target.overrideKey) : false; const fieldsLocked = target !== null && (target.groupKey === "coreValues" || isMethodFacetGroup(target.groupKey)) && !modalEditUnlocked; const showMethodModalPrimary = !isChipInSelection || modalEditUnlocked; const showCoreModalPrimary = modalEditUnlocked; useEffect(() => { if (!isOpen || !target) return; if (modalEditUnlocked) { return; } const sig = facetSeedSignature(target, state); const targetKey = `${target.groupKey}:${target.overrideKey}:${sig}`; if (seededTargetRef.current === targetKey) { return; } const seed = seedDraftForTarget(target, state); setDraft(seed); initialSnapshotRef.current = JSON.stringify(seed.value); if (target.groupKey === "coreValues") { setModalEditUnlocked(false); setCustomizeHeaderDraft(null); coreCustomizeSnapshotRef.current = null; } if (isMethodFacetGroup(target.groupKey)) { setModalEditUnlocked(false); setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); customizeSnapshotRef.current = null; } seededTargetRef.current = targetKey; }, [isOpen, target, state, modalEditUnlocked]); useEffect(() => { if (!isOpen) seededTargetRef.current = null; }, [isOpen]); const coreCustomizeSaveDisabled = useMemo(() => { if (!modalEditUnlocked) return false; const snap = coreCustomizeSnapshotRef.current; if (!snap || !draft || draft.groupKey !== "coreValues") return true; return !isMethodCardCustomizeSessionDirty( snap, draft.value, null, customizeHeaderDraft, ); }, [customizeHeaderDraft, draft, modalEditUnlocked]); const methodCustomizeSaveDisabled = useMemo(() => { if (!modalEditUnlocked) return false; const snap = customizeSnapshotRef.current; if (!snap) return true; return !isMethodCardCustomizeSessionDirty( snap, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, ); }, [ customizeHeaderDraft, draft, draftFieldBlocks, modalEditUnlocked, ]); const finalizeModalClose = useCallback(() => { customizeSnapshotRef.current = null; coreCustomizeSnapshotRef.current = null; pendingEphemeralCoreDuplicateRef.current = null; setModalEditUnlocked(false); setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); onClose(); }, [onClose]); const handleModalClose = useCallback(async () => { if ( target && target.groupKey === "coreValues" && !(await confirmDiscard( modalEditUnlocked, coreCustomizeSnapshotRef.current, draft?.groupKey === "coreValues" ? draft.value : null, null, customizeHeaderDraft, )) ) { return; } if ( target && isMethodFacetGroup(target.groupKey) && !(await confirmDiscard( modalEditUnlocked, customizeSnapshotRef.current, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, )) ) { return; } const ep = pendingEphemeralCoreDuplicateRef.current; if (ep) { replaceState((prev) => ({ ...prev, ...removeCoreValueChipFromDraft(prev, ep), })); } finalizeModalClose(); }, [ confirmDiscard, customizeHeaderDraft, draft, draftFieldBlocks, finalizeModalClose, modalEditUnlocked, replaceState, target, ]); const handleCancelCustomize = useCallback(async () => { if (!modalEditUnlocked || !target) { return; } if (target.groupKey === "coreValues") { const snap = coreCustomizeSnapshotRef.current; if (!snap) { coreCustomizeSnapshotRef.current = null; setModalEditUnlocked(false); setCustomizeHeaderDraft(null); return; } if ( draft?.groupKey === "coreValues" && !(await confirmDirtyCustomizeCancel( snap, draft.value, null, customizeHeaderDraft, )) ) { return; } setDraft({ groupKey: "coreValues", value: structuredClone(snap.pendingDraft), }); coreCustomizeSnapshotRef.current = null; setModalEditUnlocked(false); setCustomizeHeaderDraft(null); return; } if (!isMethodFacetGroup(target.groupKey)) { return; } const snap = customizeSnapshotRef.current; if (!snap) { customizeSnapshotRef.current = null; setModalEditUnlocked(false); setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); return; } if ( !(await confirmDirtyCustomizeCancel( snap, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, )) ) { return; } setPendingDraftFromSnapshot(snap); setDraftFieldBlocks(null); setModalEditUnlocked(false); customizeSnapshotRef.current = null; setCustomizeHeaderDraft(null); }, [ confirmDirtyCustomizeCancel, customizeHeaderDraft, draft, draftFieldBlocks, modalEditUnlocked, target, ]); function setPendingDraftFromSnapshot( snap: MethodCardCustomizeSnapshot< | CommunicationMethodDetailEntry | MembershipMethodDetailEntry | DecisionApproachDetailEntry | ConflictManagementDetailEntry >, ) { const v = structuredClone(snap.pendingDraft); if (!target || !isMethodFacetGroup(target.groupKey)) return; switch (target.groupKey) { case "communication": setDraft({ groupKey: "communication", value: v as CommunicationMethodDetailEntry }); break; case "membership": setDraft({ groupKey: "membership", value: v as MembershipMethodDetailEntry }); break; case "decisionApproaches": setDraft({ groupKey: "decisionApproaches", value: v as DecisionApproachDetailEntry, }); break; case "conflictManagement": setDraft({ groupKey: "conflictManagement", value: v as ConflictManagementDetailEntry, }); break; default: { const _e: never = target.groupKey; void _e; } } } const handleCustomize = useCallback(() => { onInteract?.(); if (target?.groupKey === "coreValues") { if (!draft || draft.groupKey !== "coreValues") return; const headerDraft: MethodCardHeaderDraft = { title: target.chipLabel, description: "", }; coreCustomizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( draft.value, null, headerDraft, ); setCustomizeHeaderDraft(headerDraft); setModalEditUnlocked(true); return; } const pending = pendingDraftForCustomize(draft, target); if (!pending) return; const { groupKey, pendingValue } = pending; const pendingCardId = target!.overrideKey; const initialFieldBlocks = isCustomMethodCardId( pendingCardId, state.customMethodCardMetaById, ) ? structuredClone( state.customMethodCardFieldBlocksById?.[pendingCardId] ?? [], ) : null; const method = methodById.get(pendingCardId); const meta = state.customMethodCardMetaById?.[pendingCardId]; const confirm = confirmCopyForMethodGroup(groupKey, { tComm, tMem, tDa, tCm, }); const headerDraft: MethodCardHeaderDraft = { title: meta?.label ?? method?.label ?? target!.chipLabel ?? confirm.title, description: meta?.supportText ?? method?.supportText ?? confirm.description, }; setCustomizeHeaderDraft(headerDraft); customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( pendingValue, initialFieldBlocks, headerDraft, ); setDraftFieldBlocks(initialFieldBlocks); setModalEditUnlocked(true); }, [ draft, methodById, onInteract, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, target, tComm, tMem, tDa, tCm, ]); const handleRemoveSelectedFromModal = useCallback(async () => { if (!target || !isMethodFacetGroup(target.groupKey)) { return; } const methodGroupKey = target.groupKey; if (!selectionIdsForTarget.includes(target.overrideKey)) { return; } onInteract?.(); if ( !(await confirmDiscard( modalEditUnlocked, customizeSnapshotRef.current, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, )) ) { return; } customizeSnapshotRef.current = null; replaceState((prev) => ({ ...prev, ...removeMethodCardFromFacetSelection( prev, methodGroupKey, target.overrideKey, ), })); finalizeModalClose(); }, [ confirmDiscard, customizeHeaderDraft, draft, draftFieldBlocks, finalizeModalClose, modalEditUnlocked, onInteract, replaceState, selectionIdsForTarget, target, ]); const handleRemoveCoreValueFromModal = useCallback(async () => { if (!target || target.groupKey !== "coreValues") { return; } onInteract?.(); if ( !(await confirmDiscard( modalEditUnlocked, coreCustomizeSnapshotRef.current, draft?.groupKey === "coreValues" ? draft.value : null, null, customizeHeaderDraft, )) ) { return; } coreCustomizeSnapshotRef.current = null; customizeSnapshotRef.current = null; replaceState((prev) => ({ ...prev, ...removeCoreValueChipFromDraft(prev, target.overrideKey), })); finalizeModalClose(); }, [ confirmDiscard, customizeHeaderDraft, draft, finalizeModalClose, modalEditUnlocked, onInteract, replaceState, target, ]); const handleDuplicateCoreValue = useCallback(async () => { if ( !target || target.groupKey !== "coreValues" || draft?.groupKey !== "coreValues" ) { return; } if ((state.editingPublishedRuleId?.trim() ?? "") !== "") { return; } if ((state.selectedCoreValueIds ?? []).length >= 5) { return; } if ( !(await confirmDiscard( modalEditUnlocked, coreCustomizeSnapshotRef.current, draft.value, null, customizeHeaderDraft, )) ) { return; } onInteract?.(); const priorEphemeral = pendingEphemeralCoreDuplicateRef.current; let outcome: ReturnType< typeof duplicateCoreValueChipInDraft > | null = null; replaceState((prev) => { const base = priorEphemeral != null ? { ...prev, ...removeCoreValueChipFromDraft(prev, priorEphemeral) } : prev; const res = duplicateCoreValueChipInDraft( base, target.overrideKey, modalKebabMenu.duplicateTitleSuffix, ); if (!res) { return prev; } outcome = res; return { ...base, ...res.patch }; }); if (!outcome) { return; } customizeSnapshotRef.current = null; coreCustomizeSnapshotRef.current = null; setModalEditUnlocked(false); setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); pendingEphemeralCoreDuplicateRef.current = outcome.newId; seededTargetRef.current = null; setDraft({ groupKey: "coreValues", value: structuredClone(draft.value), }); onEditTargetChange?.({ overrideKey: outcome.newId, groupKey: "coreValues", chipLabel: outcome.newLabel, }); }, [ confirmDiscard, customizeHeaderDraft, draft, modalEditUnlocked, modalKebabMenu.duplicateTitleSuffix, onEditTargetChange, onInteract, replaceState, state.editingPublishedRuleId, state.selectedCoreValueIds, target, ]); const kebabMenuItems = useMemo(() => { if (!target) return []; if (target.groupKey === "coreValues") { return buildCustomRuleModalKebabMenu(modalKebabMenu, { showCustomize: !modalEditUnlocked, onCustomize: handleCustomize, onDuplicate: (state.editingPublishedRuleId?.trim() ?? "") !== "" || (state.selectedCoreValueIds ?? []).length >= 5 ? undefined : handleDuplicateCoreValue, showRemove: true, onRemove: handleRemoveCoreValueFromModal, }); } if (!isMethodFacetGroup(target.groupKey)) return []; return buildCustomRuleModalKebabMenu(modalKebabMenu, { showCustomize: !modalEditUnlocked, onCustomize: handleCustomize, showRemove: isChipInSelection, onRemove: handleRemoveSelectedFromModal, }); }, [ handleCustomize, handleDuplicateCoreValue, handleRemoveCoreValueFromModal, handleRemoveSelectedFromModal, isChipInSelection, modalEditUnlocked, modalKebabMenu, state.editingPublishedRuleId, state.selectedCoreValueIds, target, ]); const subtitle = useMemo(() => { if (!target) return ""; return subtitleForTarget( target, { tCv, tComm, tMem, tDa, tCm }, state.customMethodCardMetaById, ); }, [target, tCv, tComm, tMem, tDa, tCm, state.customMethodCardMetaById]); const handleCoreSave = useCallback(() => { if (!target || !draft || draft.groupKey !== "coreValues") { return; } if (!modalEditUnlocked || !customizeHeaderDraft) { return; } if (coreCustomizeSaveDisabled) { return; } const labelTrim = customizeHeaderDraft.title.trim(); onInteract?.(); onSave({ groupKey: "coreValues", overrideKey: target.overrideKey, value: structuredClone(draft.value), ...(labelTrim.length > 0 ? { chipLabel: labelTrim } : {}), }); coreCustomizeSnapshotRef.current = null; setModalEditUnlocked(false); setCustomizeHeaderDraft(null); initialSnapshotRef.current = JSON.stringify(draft.value); pendingEphemeralCoreDuplicateRef.current = null; onEditTargetChange?.({ overrideKey: target.overrideKey, groupKey: "coreValues", chipLabel: labelTrim.length > 0 ? labelTrim : target.chipLabel, }); }, [ coreCustomizeSaveDisabled, customizeHeaderDraft, draft, modalEditUnlocked, onEditTargetChange, onInteract, onSave, target, ]); const handleMethodPrimary = useCallback(() => { if (!target || !draft || !isMethodFacetGroup(target.groupKey)) return; const facet = CUSTOM_RULE_FACET_BY_GROUP.get(target.groupKey)!; const pendingId = target.overrideKey; const sel = [...facet.selectionIds(state)]; if (!modalEditUnlocked) { if (!sel.includes(pendingId)) { onInteract?.(); replaceState((prev) => ({ ...prev, [facet.selectedIdsStateKey]: moveFacetSelectionIdToFront( [...facet.selectionIds(prev)], pendingId, ), })); onClose(); } return; } if (!customizeHeaderDraft) return; if (!isMethodFacetGroup(draft.groupKey)) return; onInteract?.(); const header = customizeHeaderDraft; const metaSave = { label: header.title, supportText: header.description, }; const useWizard = usesWizardFieldBlocksModalBody({ methodId: pendingId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, modalEditUnlocked, draftFieldBlocks, }); const blocksPayload = useWizard ? structuredClone(draftFieldBlocks ?? []) : undefined; switch (draft.groupKey) { case "communication": onSave({ groupKey: "communication", overrideKey: pendingId, value: draft.value, methodCardMeta: metaSave, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "membership": onSave({ groupKey: "membership", overrideKey: pendingId, value: draft.value, methodCardMeta: metaSave, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "decisionApproaches": onSave({ groupKey: "decisionApproaches", overrideKey: pendingId, value: draft.value, methodCardMeta: metaSave, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "conflictManagement": onSave({ groupKey: "conflictManagement", overrideKey: pendingId, value: draft.value, methodCardMeta: metaSave, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; } customizeSnapshotRef.current = null; setModalEditUnlocked(false); setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); }, [ customizeHeaderDraft, draft, draftFieldBlocks, modalEditUnlocked, onClose, onInteract, onSave, replaceState, state, target, ]); const handleNext = useCallback(() => { if (!target || !draft) return; if (target.groupKey === "coreValues") { handleCoreSave(); } else { handleMethodPrimary(); } }, [draft, handleCoreSave, handleMethodPrimary, target]); const nextButtonText = useMemo(() => { if (!target) return tModal("saveButton"); if (target.groupKey === "coreValues" && modalEditUnlocked) { return modalKebabMenu.saveEdits; } if (target.groupKey === "coreValues") { return tModal("saveButton"); } if (modalEditUnlocked) return modalKebabMenu.saveEdits; if (!isChipInSelection) return addPrimaryLabelForMethodFacet(target.groupKey, cr); return tModal("saveButton"); }, [ cr, isChipInSelection, modalEditUnlocked, modalKebabMenu.saveEdits, target, tModal, ]); const headerContent = useMemo(() => { if ( target && target.groupKey === "coreValues" && modalEditUnlocked && customizeHeaderDraft ) { return ( setCustomizeHeaderDraft((prev) => prev ? { ...prev, title } : null, ) } onDescriptionChange={() => {}} showDescription={false} /> ); } if ( target && isMethodFacetGroup(target.groupKey) && modalEditUnlocked && customizeHeaderDraft ) { return ( setCustomizeHeaderDraft((prev) => prev ? { ...prev, title } : null, ) } onDescriptionChange={(description) => setCustomizeHeaderDraft((prev) => prev ? { ...prev, description } : null, ) } /> ); } if (!target) return undefined; return (
); }, [ customizeHeaderDraft, modalEditUnlocked, modalKebabMenu.customizePolicyDescriptionLabel, modalKebabMenu.customizePolicyTitleLabel, subtitle, target, tCv.detailModal.customizeValueNameLabel, ]); const showNext = target?.groupKey === "coreValues" ? showCoreModalPrimary : showMethodModalPrimary; return ( <> 0 ? kebabMenuItems : undefined } ariaLabel={target?.chipLabel || "Edit chip details"} >
{draft?.groupKey === "coreValues" && ( setDraft({ groupKey: "coreValues", value })} /> )} {draft?.groupKey === "communication" && target && (isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) ? ( setDraftFieldBlocks(next) } /> ) : ( setDraft({ groupKey: "communication", value }) } readOnly={fieldsLocked} /> ))} {draft?.groupKey === "membership" && target && (isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) ? ( setDraftFieldBlocks(next) } /> ) : ( setDraft({ groupKey: "membership", value }) } readOnly={fieldsLocked} /> ))} {draft?.groupKey === "decisionApproaches" && target && (isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) ? ( setDraftFieldBlocks(next) } /> ) : ( setDraft({ groupKey: "decisionApproaches", value }) } readOnly={fieldsLocked} /> ))} {draft?.groupKey === "conflictManagement" && target && (isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) ? ( setDraftFieldBlocks(next) } /> ) : ( setDraft({ groupKey: "conflictManagement", value }) } readOnly={fieldsLocked} /> ))}
{confirmDialog} ); } // ---------- helpers ------------------------------------------------------ function facetSeedSignature( target: FinalReviewChipEditTarget, state: CreateFlowState, ): string { const id = target.overrideKey; switch (target.groupKey) { case "coreValues": return JSON.stringify({ details: state.coreValueDetailsByChipId?.[id], row: state.coreValuesChipsSnapshot?.find((r) => r.id === id) ?? null, }); case "communication": return JSON.stringify({ meta: state.customMethodCardMetaById?.[id] ?? null, details: state.communicationMethodDetailsById?.[id] ?? null, blocks: state.customMethodCardFieldBlocksById?.[id] ?? null, }); case "membership": return JSON.stringify({ meta: state.customMethodCardMetaById?.[id] ?? null, details: state.membershipMethodDetailsById?.[id] ?? null, blocks: state.customMethodCardFieldBlocksById?.[id] ?? null, }); case "decisionApproaches": return JSON.stringify({ meta: state.customMethodCardMetaById?.[id] ?? null, details: state.decisionApproachDetailsById?.[id] ?? null, blocks: state.customMethodCardFieldBlocksById?.[id] ?? null, }); case "conflictManagement": return JSON.stringify({ meta: state.customMethodCardMetaById?.[id] ?? null, details: state.conflictManagementDetailsById?.[id] ?? null, blocks: state.customMethodCardFieldBlocksById?.[id] ?? null, }); default: { const _e: never = target.groupKey; return String(_e); } } } function pendingDraftForCustomize( draft: Draft | null, target: FinalReviewChipEditTarget | null, ): { groupKey: MethodFacetGroupKey; pendingValue: MethodDetailDraft } | null { if (!draft || !target || !isMethodFacetGroup(target.groupKey)) return null; switch (draft.groupKey) { case "coreValues": return null; case "communication": return { groupKey: "communication", pendingValue: draft.value }; case "membership": return { groupKey: "membership", pendingValue: draft.value }; case "decisionApproaches": return { groupKey: "decisionApproaches", pendingValue: draft.value }; case "conflictManagement": return { groupKey: "conflictManagement", pendingValue: draft.value }; } } function confirmCopyForMethodGroup( groupKey: MethodFacetGroupKey, t: { tComm: { confirmModal: { title: string; description: string } }; tMem: { confirmModal: { title: string; description: string } }; tDa: { confirmModal: { title: string; description: string } }; tCm: { confirmModal: { title: string; description: string } }; }, ) { switch (groupKey) { case "communication": return t.tComm.confirmModal; case "membership": return t.tMem.confirmModal; case "decisionApproaches": return t.tDa.confirmModal; case "conflictManagement": return t.tCm.confirmModal; } } function addPrimaryLabelForMethodFacet( groupKey: MethodFacetGroupKey, cr: ReturnType["create"]["customRule"], ): string { switch (groupKey) { case "communication": return cr.communication.addPlatform.nextButtonText; case "membership": return cr.membership.addPlatform.nextButtonText; case "decisionApproaches": return cr.decisionApproaches.addApproach.nextButtonText; case "conflictManagement": return cr.conflictManagement.addApproach.nextButtonText; } } function seedDraftForTarget( target: FinalReviewChipEditTarget, state: CreateFlowState, ): Draft { switch (target.groupKey) { case "coreValues": { const saved = state.coreValueDetailsByChipId?.[target.overrideKey]; const preset = coreValuePresetFor(target.overrideKey); return { groupKey: "coreValues", value: { meaning: saved?.meaning ?? preset.meaning, signals: saved?.signals ?? preset.signals, }, }; } case "communication": { const saved = state.communicationMethodDetailsById?.[target.overrideKey] ?? communicationPresetFor(target.overrideKey); return { groupKey: "communication", value: { ...saved } }; } case "membership": { const saved = state.membershipMethodDetailsById?.[target.overrideKey] ?? membershipPresetFor(target.overrideKey); return { groupKey: "membership", value: { ...saved } }; } case "decisionApproaches": { const saved = state.decisionApproachDetailsById?.[target.overrideKey] ?? decisionApproachPresetFor(target.overrideKey); return { groupKey: "decisionApproaches", value: { ...saved, applicableScope: [...saved.applicableScope], selectedApplicableScope: [...saved.selectedApplicableScope], }, }; } case "conflictManagement": { const saved = state.conflictManagementDetailsById?.[target.overrideKey] ?? conflictManagementPresetFor(target.overrideKey); return { groupKey: "conflictManagement", value: { ...saved, applicableScope: [...saved.applicableScope], selectedApplicableScope: [...saved.selectedApplicableScope], }, }; } } } type SubtitleMessages = { tCv: ReturnType["create"]["customRule"]["coreValues"]; tComm: ReturnType["create"]["customRule"]["communication"]; tMem: ReturnType["create"]["customRule"]["membership"]; tDa: ReturnType< typeof useMessages >["create"]["customRule"]["decisionApproaches"]; tCm: ReturnType< typeof useMessages >["create"]["customRule"]["conflictManagement"]; }; function subtitleForTarget( target: FinalReviewChipEditTarget, msgs: SubtitleMessages, customMeta?: CreateFlowState["customMethodCardMetaById"], ): string { switch (target.groupKey) { case "coreValues": return msgs.tCv.detailModal.subtitle; case "communication": { const fromCustom = customMeta?.[target.overrideKey]?.supportText?.trim(); if (fromCustom) return fromCustom; return findMethodSupportText(msgs.tComm.methods, target.overrideKey); } case "membership": { const fromCustom = customMeta?.[target.overrideKey]?.supportText?.trim(); if (fromCustom) return fromCustom; return findMethodSupportText(msgs.tMem.methods, target.overrideKey); } case "decisionApproaches": { const fromCustom = customMeta?.[target.overrideKey]?.supportText?.trim(); if (fromCustom) return fromCustom; return findMethodSupportText(msgs.tDa.methods, target.overrideKey); } case "conflictManagement": { const fromCustom = customMeta?.[target.overrideKey]?.supportText?.trim(); if (fromCustom) return fromCustom; return findMethodSupportText(msgs.tCm.methods, target.overrideKey); } } } function findMethodSupportText( methods: readonly { id: string; supportText: string }[], id: string, ): string { for (const method of methods) { if (method.id === id) return method.supportText; } return ""; }