"use client"; /** * Final-review chip modal: **Core values** and **method** facets share the * kebab → **Duplicate** (values only when under the cap) / **Remove** pattern * from the create-card facet modals (`Create` + * {@link buildCustomRuleModalKebabMenu}). Values and method chips also offer * **Customize**, which opens {@link CustomMethodCardWizard} prefilled from the * chip. Fields are editable on open; Save persists body edits without renaming. * * 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 { buildCustomRuleModalKebabMenu } from "./customRuleModalKebabMenu"; import { useDiscardCustomizeConfirm } from "../hooks/useDiscardCustomizeConfirm"; import { useBeforeUnloadGuard } from "../../../hooks/useBeforeUnloadGuard"; import { communicationPresetFor, conflictManagementPresetFor, coreValuePresetFor, decisionApproachPresetFor, membershipPresetFor, } from "../../../../lib/create/finalReviewChipPresets"; import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId"; import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody"; import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks"; import { CUSTOM_RULE_FACET_BY_GROUP, isCatalogMethodCardId, 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 { buildMethodCardWizardInitialValues, coreValueDetailsFromWizardFieldBlocks, overlayFacetPrefillValues, } from "../../../../lib/create/methodCardWizardPrefill"; import type { MethodCardWizardFacetPrefill, MethodCardWizardInitialValues, } from "../../../../lib/create/methodCardWizardPrefill"; import { uploadCreateFlowFile } from "../../../../lib/create/uploadToServer"; import CustomMethodCardWizard from "./CustomMethodCardWizard"; 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; customMethodCardFieldBlocks?: CustomMethodCardFieldBlock[]; } | { 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, confirmDialog } = useDiscardCustomizeConfirm(); const [draft, setDraft] = useState(null); const [draftFieldBlocks, setDraftFieldBlocks] = useState< CustomMethodCardFieldBlock[] | null >(null); const [customizeHeaderDraft, setCustomizeHeaderDraft] = useState(null); const [addCustomWizardOpen, setAddCustomWizardOpen] = useState(false); const [wizardCustomizeCardId, setWizardCustomizeCardId] = useState< string | null >(null); const [wizardInitialValues, setWizardInitialValues] = 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; useEffect(() => { if (!isOpen || !target) 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") { const persisted = state.customMethodCardFieldBlocksById?.[target.overrideKey]; const initialBlocks = Array.isArray(persisted) && persisted.length > 0 ? structuredClone(persisted) : null; const headerDraft: MethodCardHeaderDraft = { title: target.chipLabel, description: "", }; setCustomizeHeaderDraft(headerDraft); coreCustomizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( seed.value as CoreValueDetailEntry, initialBlocks, headerDraft, ); setDraftFieldBlocks(initialBlocks); } if (isMethodFacetGroup(target.groupKey) && seed.groupKey !== "coreValues") { const persisted = state.customMethodCardFieldBlocksById?.[target.overrideKey]; const initialBlocks = Array.isArray(persisted) && persisted.length > 0 ? structuredClone(persisted) : isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) ? structuredClone(persisted ?? []) : null; const method = methodById.get(target.overrideKey); const meta = state.customMethodCardMetaById?.[target.overrideKey]; const headerDraft: MethodCardHeaderDraft = { title: meta?.label ?? method?.label ?? target.chipLabel, description: meta?.supportText ?? method?.supportText ?? "", }; setCustomizeHeaderDraft(headerDraft); customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( seed.value as MethodDetailDraft, initialBlocks, headerDraft, ); setDraftFieldBlocks(initialBlocks); } seededTargetRef.current = targetKey; }, [isOpen, target, state, methodById]); useEffect(() => { if (!isOpen) seededTargetRef.current = null; }, [isOpen]); const coreCustomizeSaveDisabled = useMemo(() => { const snap = coreCustomizeSnapshotRef.current; if (!snap || !draft || draft.groupKey !== "coreValues") return true; return !isMethodCardCustomizeSessionDirty( snap, draft.value, draftFieldBlocks, customizeHeaderDraft, ); }, [customizeHeaderDraft, draft, draftFieldBlocks]); const methodCustomizeSaveDisabled = useMemo(() => { const snap = customizeSnapshotRef.current; if (!snap) return true; return !isMethodCardCustomizeSessionDirty( snap, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, ); }, [ customizeHeaderDraft, draft, draftFieldBlocks, ]); useBeforeUnloadGuard( isOpen && !addCustomWizardOpen && (!coreCustomizeSaveDisabled || !methodCustomizeSaveDisabled), ); const modalUsesWizardFieldBlocksBody = Boolean( target && (usesWizardFieldBlocksModalBody({ methodId: target.overrideKey, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, modalEditUnlocked: true, draftFieldBlocks, }) || (isCustomMethodCardId( target.overrideKey, state.customMethodCardMetaById, ) && !isCatalogMethodCardId(target.overrideKey))), ); const handleWizardFieldBlocksChange = useCallback( (next: CustomMethodCardFieldBlock[]) => { setDraftFieldBlocks(next); setDraft((prev) => { if (!prev || prev.groupKey !== "coreValues") { return prev; } return { groupKey: "coreValues", value: coreValueDetailsFromWizardFieldBlocks(next, prev.value), }; }); }, [], ); const finalizeModalClose = useCallback(() => { customizeSnapshotRef.current = null; coreCustomizeSnapshotRef.current = null; pendingEphemeralCoreDuplicateRef.current = null; setDraftFieldBlocks(null); setCustomizeHeaderDraft(null); setAddCustomWizardOpen(false); setWizardCustomizeCardId(null); setWizardInitialValues(null); onClose(); }, [onClose]); const handleModalClose = useCallback(async () => { if ( target && target.groupKey === "coreValues" && !(await confirmDiscard( true, coreCustomizeSnapshotRef.current, draft?.groupKey === "coreValues" ? draft.value : null, draftFieldBlocks, customizeHeaderDraft, )) ) { return; } if ( target && isMethodFacetGroup(target.groupKey) && !(await confirmDiscard( true, 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, replaceState, target, ]); const handleCustomize = useCallback(() => { onInteract?.(); if (!target) { return; } if (target.groupKey === "coreValues") { const value = draft?.groupKey === "coreValues" ? draft.value : coreValuePresetFor(target.overrideKey); setWizardInitialValues( buildMethodCardWizardInitialValues({ cardId: target.overrideKey, fallbackTitle: target.chipLabel, fallbackDescription: value.supportText?.trim() || tCv.detailModal.subtitle, meta: {}, persistedBlocks: state.customMethodCardFieldBlocksById, draftFieldBlocks: null, facetPrefill: { group: "coreValues", draft: value, headings: { meaning: tCv.detailModal.meaningLabel, signals: tCv.detailModal.signalsLabel, }, }, }), ); setWizardCustomizeCardId(target.overrideKey); setAddCustomWizardOpen(true); return; } if (!isMethodFacetGroup(target.groupKey)) { return; } const method = methodById.get(target.overrideKey); const meta = state.customMethodCardMetaById?.[target.overrideKey]; let facetPrefill: MethodCardWizardFacetPrefill | undefined; if (draft && draft.groupKey === "communication") { facetPrefill = { group: "communication", draft: draft.value, headings: tComm.sectionHeadings, }; } else if (draft && draft.groupKey === "membership") { facetPrefill = { group: "membership", draft: draft.value, headings: tMem.sectionHeadings, }; } else if (draft && draft.groupKey === "decisionApproaches") { facetPrefill = { group: "decisionApproaches", draft: draft.value, headings: tDa.sectionHeadings, }; } else if (draft && draft.groupKey === "conflictManagement") { facetPrefill = { group: "conflictManagement", draft: draft.value, headings: tCm.sectionHeadings, }; } setWizardInitialValues( buildMethodCardWizardInitialValues({ cardId: target.overrideKey, fallbackTitle: method?.label ?? target.chipLabel, fallbackDescription: method?.supportText ?? meta?.supportText ?? "", meta: state.customMethodCardMetaById, persistedBlocks: state.customMethodCardFieldBlocksById, draftFieldBlocks, facetPrefill, }), ); setWizardCustomizeCardId(target.overrideKey); setAddCustomWizardOpen(true); }, [ draft, draftFieldBlocks, methodById, onInteract, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, tCm.sectionHeadings, tComm.sectionHeadings, tCv.detailModal.meaningLabel, tCv.detailModal.signalsLabel, tCv.detailModal.subtitle, tDa.sectionHeadings, tMem.sectionHeadings, target, ]); const handleRemoveSelectedFromModal = useCallback(async () => { if (!target || !isMethodFacetGroup(target.groupKey)) { return; } const methodGroupKey = target.groupKey; if (!selectionIdsForTarget.includes(target.overrideKey)) { return; } onInteract?.(); if ( !(await confirmDiscard( true, customizeSnapshotRef.current, methodDetailDraftForCustomizeSession(draft), draftFieldBlocks, customizeHeaderDraft, )) ) { return; } customizeSnapshotRef.current = null; replaceState((prev) => ({ ...prev, ...removeMethodCardFromFacetSelection( prev, methodGroupKey, target.overrideKey, ), })); finalizeModalClose(); }, [ confirmDiscard, customizeHeaderDraft, draft, draftFieldBlocks, finalizeModalClose, onInteract, replaceState, selectionIdsForTarget, target, ]); const handleRemoveCoreValueFromModal = useCallback(async () => { if (!target || target.groupKey !== "coreValues") { return; } onInteract?.(); if ( !(await confirmDiscard( true, coreCustomizeSnapshotRef.current, draft?.groupKey === "coreValues" ? draft.value : null, draftFieldBlocks, customizeHeaderDraft, )) ) { return; } coreCustomizeSnapshotRef.current = null; customizeSnapshotRef.current = null; replaceState((prev) => ({ ...prev, ...removeCoreValueChipFromDraft(prev, target.overrideKey), })); finalizeModalClose(); }, [ confirmDiscard, customizeHeaderDraft, draft, draftFieldBlocks, finalizeModalClose, 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( true, coreCustomizeSnapshotRef.current, draft.value, draftFieldBlocks, 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; 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, draftFieldBlocks, modalKebabMenu.duplicateTitleSuffix, onEditTargetChange, onInteract, replaceState, state.editingPublishedRuleId, state.selectedCoreValueIds, target, ]); const kebabMenuItems = useMemo(() => { if (!target) return []; if (target.groupKey === "coreValues") { return buildCustomRuleModalKebabMenu(modalKebabMenu, { showCustomize: true, 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: true, onCustomize: handleCustomize, showRemove: isChipInSelection, onRemove: handleRemoveSelectedFromModal, }); }, [ handleCustomize, handleDuplicateCoreValue, handleRemoveCoreValueFromModal, handleRemoveSelectedFromModal, isChipInSelection, modalKebabMenu, state.editingPublishedRuleId, state.selectedCoreValueIds, target, ]); const subtitle = useMemo(() => { if (!target) return ""; return subtitleForTarget( target, { tCv, tComm, tMem, tDa, tCm }, state.customMethodCardMetaById, state.coreValueDetailsByChipId?.[target.overrideKey]?.supportText, ); }, [ target, tCv, tComm, tMem, tDa, tCm, state.customMethodCardMetaById, state.coreValueDetailsByChipId, ]); const handleCoreSave = useCallback(() => { if (!target || !draft || draft.groupKey !== "coreValues") { return; } if (coreCustomizeSaveDisabled) { return; } onInteract?.(); const existingBlocks = state.customMethodCardFieldBlocksById?.[target.overrideKey]; onSave({ groupKey: "coreValues", overrideKey: target.overrideKey, value: structuredClone(draft.value), ...(existingBlocks && existingBlocks.length > 0 ? { customMethodCardFieldBlocks: overlayFacetPrefillValues( existingBlocks, { group: "coreValues", draft: draft.value, headings: { meaning: tCv.detailModal.meaningLabel, signals: tCv.detailModal.signalsLabel, }, }, ), } : {}), }); coreCustomizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( draft.value, draftFieldBlocks, customizeHeaderDraft ?? { title: target.chipLabel, description: "", }, ); initialSnapshotRef.current = JSON.stringify(draft.value); pendingEphemeralCoreDuplicateRef.current = null; }, [ coreCustomizeSaveDisabled, customizeHeaderDraft, draft, draftFieldBlocks, onInteract, onSave, state.customMethodCardFieldBlocksById, tCv.detailModal.meaningLabel, tCv.detailModal.signalsLabel, target, ]); const handleMethodPrimary = useCallback(() => { if (!target || !draft || !isMethodFacetGroup(target.groupKey)) return; if (!isMethodFacetGroup(draft.groupKey)) return; const facet = CUSTOM_RULE_FACET_BY_GROUP.get(target.groupKey)!; const pendingId = target.overrideKey; const sel = [...facet.selectionIds(state)]; onInteract?.(); const persistWizardBlocks = draftFieldBlocks !== null; const blocksPayload = persistWizardBlocks ? structuredClone(draftFieldBlocks ?? []) : undefined; switch (draft.groupKey) { case "communication": onSave({ groupKey: "communication", overrideKey: pendingId, value: draft.value, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "membership": onSave({ groupKey: "membership", overrideKey: pendingId, value: draft.value, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "decisionApproaches": onSave({ groupKey: "decisionApproaches", overrideKey: pendingId, value: draft.value, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; case "conflictManagement": onSave({ groupKey: "conflictManagement", overrideKey: pendingId, value: draft.value, ...(blocksPayload !== undefined ? { customMethodCardFieldBlocks: blocksPayload } : {}), }); break; } if (!sel.includes(pendingId)) { replaceState((prev) => ({ ...prev, [facet.selectedIdsStateKey]: moveFacetSelectionIdToFront( [...facet.selectionIds(prev)], pendingId, ), })); finalizeModalClose(); return; } const sessionDraft = methodDetailDraftForCustomizeSession(draft); if (sessionDraft) { customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( sessionDraft, draftFieldBlocks, customizeHeaderDraft ?? { title: "", description: "" }, ); } }, [ customizeHeaderDraft, draft, draftFieldBlocks, finalizeModalClose, onInteract, onSave, replaceState, state, target, ]); const handleCloseAddWizard = useCallback(() => { setAddCustomWizardOpen(false); setWizardCustomizeCardId(null); setWizardInitialValues(null); }, []); const handleFinalizeCustomCard = useCallback( ({ title, description, fieldBlocks, }: { title: string; description: string; fieldBlocks: CustomMethodCardFieldBlock[]; }) => { if (!target || !draft) { return; } if (target.groupKey === "coreValues" && draft.groupKey === "coreValues") { const existingId = wizardCustomizeCardId ?? target.overrideKey; onInteract?.(); const details = coreValueDetailsFromWizardFieldBlocks( fieldBlocks, draft.value, ); const trimmedDescription = description.trim(); onSave({ groupKey: "coreValues", overrideKey: existingId, value: { ...details, ...(trimmedDescription.length > 0 ? { supportText: trimmedDescription } : {}), }, chipLabel: title, customMethodCardFieldBlocks: structuredClone(fieldBlocks), }); setDraft({ groupKey: "coreValues", value: { ...details, ...(trimmedDescription.length > 0 ? { supportText: trimmedDescription } : {}), }, }); setDraftFieldBlocks(structuredClone(fieldBlocks)); coreCustomizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( { ...details, ...(trimmedDescription.length > 0 ? { supportText: trimmedDescription } : {}), }, fieldBlocks, { title, description }, ); setAddCustomWizardOpen(false); setWizardCustomizeCardId(null); setWizardInitialValues(null); return; } if ( !isMethodFacetGroup(target.groupKey) || !isMethodFacetGroup(draft.groupKey) ) { return; } const existingId = wizardCustomizeCardId ?? target.overrideKey; onInteract?.(); const meta = { label: title, supportText: description }; const blocks = structuredClone(fieldBlocks); switch (draft.groupKey) { case "communication": onSave({ groupKey: "communication", overrideKey: existingId, value: draft.value, methodCardMeta: meta, customMethodCardFieldBlocks: blocks, }); break; case "membership": onSave({ groupKey: "membership", overrideKey: existingId, value: draft.value, methodCardMeta: meta, customMethodCardFieldBlocks: blocks, }); break; case "decisionApproaches": onSave({ groupKey: "decisionApproaches", overrideKey: existingId, value: draft.value, methodCardMeta: meta, customMethodCardFieldBlocks: blocks, }); break; case "conflictManagement": onSave({ groupKey: "conflictManagement", overrideKey: existingId, value: draft.value, methodCardMeta: meta, customMethodCardFieldBlocks: blocks, }); break; } customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( draft.value, blocks, { title, description }, ); setDraftFieldBlocks(blocks); setAddCustomWizardOpen(false); setWizardCustomizeCardId(null); setWizardInitialValues(null); }, [ draft, onInteract, onSave, target, wizardCustomizeCardId, ], ); 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") { return tModal("saveButton"); } if (!isChipInSelection) { return addPrimaryLabelForMethodFacet(target.groupKey, cr); } return tModal("saveButton"); }, [cr, isChipInSelection, target, tModal]); const headerContent = useMemo(() => { if (!target) return undefined; return (
); }, [subtitle, target]); return ( <> 0 ? kebabMenuItems : undefined } ariaLabel={target?.chipLabel || "Edit chip details"} >
{draft?.groupKey === "coreValues" && target && (modalUsesWizardFieldBlocksBody ? ( ) : ( setDraft({ groupKey: "coreValues", value })} /> ))} {draft?.groupKey === "communication" && target && (modalUsesWizardFieldBlocksBody ? ( ) : ( setDraft({ groupKey: "communication", value }) } /> ))} {draft?.groupKey === "membership" && target && (modalUsesWizardFieldBlocksBody ? ( ) : ( setDraft({ groupKey: "membership", value }) } /> ))} {draft?.groupKey === "decisionApproaches" && target && (modalUsesWizardFieldBlocksBody ? ( ) : ( setDraft({ groupKey: "decisionApproaches", value }) } /> ))} {draft?.groupKey === "conflictManagement" && target && (modalUsesWizardFieldBlocksBody ? ( ) : ( setDraft({ groupKey: "conflictManagement", value }) } /> ))}
uploadCreateFlowFile(file, "customMethodAttachment") } /> {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, blocks: state.customMethodCardFieldBlocksById?.[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 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, ...(saved?.supportText ? { supportText: saved.supportText } : {}), }, }; } 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"], coreValueSupportText?: string, ): string { switch (target.groupKey) { case "coreValues": { const fromSaved = coreValueSupportText?.trim(); if (fromSaved) return fromSaved; 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 ""; }