"use client"; /** * `conflict-management` step — Figma compact card stack (node `20879-15979`). * Registry: `CREATE_FLOW_SCREEN_REGISTRY["conflict-management"]`. * * Card click opens the Figma "Add Approach" create modal (node `20874-172292`) * with four controls rendered by {@link ConflictManagementEditFields}: Core * Principle, Applicable Scope (text area), Process Protocol, and Restoration * & Fallbacks. The same field set is reused on `/create/final-review` — see * `FinalReviewChipEditModal`. Confirm persists both the chip selection and * any user edits as a `conflictManagementDetailsById[id]` override. */ import { useState, useCallback, useMemo, useRef } from "react"; import { useMessages } from "../../../../contexts/MessagesContext"; import { useCreateFlow } from "../../context/CreateFlowContext"; import { useCreateFlowMdUp } from "../../hooks/useCreateFlowMdUp"; import { useBeforeUnloadGuard } from "../../../../hooks/useBeforeUnloadGuard"; import { useDiscardCustomizeConfirm } from "../../hooks/useDiscardCustomizeConfirm"; import { useMethodCardDeckOrdering } from "../../hooks/useMethodCardDeckOrdering"; import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup"; import CardStack from "../../../../components/cards/CardStack"; import Create from "../../../../components/modals/Create"; import InlineTextButton from "../../../../components/buttons/InlineTextButton"; import { CreateFlowStepShell } from "../../components/CreateFlowStepShell"; import { CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS, CREATE_FLOW_MD_CENTERED_SHELL_CLASS, CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS, } from "../../components/createFlowLayoutTokens"; import { ConflictManagementEditFields } from "../../components/methodEditFields"; import CustomMethodCardWizard from "../../components/CustomMethodCardWizard"; import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer"; import { conflictManagementPresetFor } from "../../../../../lib/create/finalReviewChipPresets"; import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks"; import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom"; import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder"; import { isCustomMethodCardId } from "../../../../../lib/create/isCustomMethodCardId"; import { conflictManagementFacetMatchesPreset } from "../../../../../lib/create/methodCardFacetMatchesPresetForId"; import { usesWizardFieldBlocksModalBody } from "../../../../../lib/create/usesWizardFieldBlocksModalBody"; import { removeMethodCardFromFacetSelection } from "../../../../../lib/create/removeMethodCardFromFacetSelection"; import { cloneMethodCardBlocksForDuplicate, cloneMethodCardDetailsForDuplicate, duplicateMethodCardTitle, forkMethodCardFacetMapsForDuplicate, omitIdFromStringRecord, } from "../../../../../lib/create/duplicateMethodCardModalDraft"; import type { ConflictManagementDetailEntry } from "../../types"; import CustomMethodCardModalBody from "../../components/CustomMethodCardModalBody"; import { buildCustomRuleModalKebabMenu } from "../../components/customRuleModalKebabMenu"; import { methodCardMetaWithCustomizeHeader } from "../../../../../lib/create/methodCardCustomizeMetaPatch"; import { buildMethodCardWizardInitialValues } from "../../../../../lib/create/methodCardWizardPrefill"; import type { MethodCardWizardInitialValues } from "../../../../../lib/create/methodCardWizardPrefill"; import { captureMethodCardCustomizeSnapshot, isMethodCardCustomizeUnloadBlocked, type MethodCardCustomizeSnapshot, type MethodCardHeaderDraft, } from "../../../../../lib/create/methodCardCustomizeSession"; export function ConflictManagementScreen() { const m = useMessages(); const cm = m.create.customRule.conflictManagement; const modalKebabMenu = m.create.customRule.modalKebabMenu; const mdUp = useCreateFlowMdUp(); const { confirmDiscard, confirmDialog } = useDiscardCustomizeConfirm(); const { state, updateState, replaceState, markCreateFlowInteraction } = useCreateFlow(); const pendingEphemeralDuplicateIdRef = useRef(null); const customizeSnapshotRef = useRef< MethodCardCustomizeSnapshot | null >(null); const [expanded, setExpanded] = useState(false); const [createModalOpen, setCreateModalOpen] = useState(false); const [pendingCardId, setPendingCardId] = useState(null); const [pendingDraft, setPendingDraft] = useState(null); const [addCustomWizardOpen, setAddCustomWizardOpen] = useState(false); const [wizardCustomizeCardId, setWizardCustomizeCardId] = useState< string | null >(null); const [wizardInitialValues, setWizardInitialValues] = useState(null); const [draftFieldBlocks, setDraftFieldBlocks] = useState< CustomMethodCardFieldBlock[] | null >(null); useBeforeUnloadGuard( isMethodCardCustomizeUnloadBlocked( createModalOpen, customizeSnapshotRef.current, pendingDraft, draftFieldBlocks, customizeSnapshotRef.current?.headerDraft ?? null, ), ); const selectedIds = state.selectedConflictManagementIds ?? []; const mergedMethods = useMemo( () => mergePresetMethodsWithCustom( cm.methods, selectedIds, state.customMethodCardMetaById, ), [cm.methods, selectedIds, state.customMethodCardMetaById], ); const { sampleCards, compactCardIds, methodById, recommendationsReady } = useMethodCardDeckOrdering( "conflictManagement", mergedMethods, selectedIds, ); const handleOpenAddWizard = useCallback(() => { markCreateFlowInteraction(); setWizardCustomizeCardId(null); setWizardInitialValues(null); setAddCustomWizardOpen(true); }, [markCreateFlowInteraction]); const title = expanded ? cm.page.expandedTitle : cm.page.compactTitle; const description = expanded ? ( <> {cm.page.expandedDescriptionBefore} {cm.page.compactDescriptionLinkLabel} {cm.page.expandedDescriptionAfter} ) : ( <> {cm.page.compactDescriptionBefore} {cm.page.compactDescriptionLinkLabel} {cm.page.compactDescriptionAfter} ); const seedDraft = useCallback( (id: string): ConflictManagementDetailEntry => { const saved = state.conflictManagementDetailsById?.[id]; if (saved) { return { ...saved, applicableScope: [...saved.applicableScope], selectedApplicableScope: [...saved.selectedApplicableScope], }; } return conflictManagementPresetFor(id); }, [state.conflictManagementDetailsById], ); const handleCardClick = useCallback( (id: string) => { markCreateFlowInteraction(); const draft = seedDraft(id); const persistedBlocks = state.customMethodCardFieldBlocksById?.[id]; const initialBlocks = Array.isArray(persistedBlocks) && persistedBlocks.length > 0 ? structuredClone(persistedBlocks) : null; const method = methodById.get(id); const meta = state.customMethodCardMetaById?.[id]; const headerDraft: MethodCardHeaderDraft = { title: meta?.label ?? method?.label ?? cm.confirmModal.title, description: meta?.supportText ?? method?.supportText ?? cm.confirmModal.description, }; customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( draft, initialBlocks, headerDraft, ); setPendingCardId(id); setPendingDraft(draft); setDraftFieldBlocks(initialBlocks); setCreateModalOpen(true); }, [ cm.confirmModal.description, cm.confirmModal.title, markCreateFlowInteraction, methodById, seedDraft, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, ], ); const handleDraftChange = useCallback( (next: ConflictManagementDetailEntry) => { markCreateFlowInteraction(); setPendingDraft(next); }, [markCreateFlowInteraction], ); const isSelectedCardModal = pendingCardId !== null && selectedIds.includes(pendingCardId); const showMethodModalPrimary = true; const customFacetDetailsMatchPreset = useMemo(() => { if (!pendingCardId || !pendingDraft) return false; if (!isCustomMethodCardId(pendingCardId, state.customMethodCardMetaById)) { return false; } return conflictManagementFacetMatchesPreset(pendingDraft, pendingCardId); }, [ pendingCardId, pendingDraft, state.customMethodCardMetaById, ]); const modalUsesWizardFieldBlocksBody = useMemo( () => Boolean( pendingCardId && usesWizardFieldBlocksModalBody({ methodId: pendingCardId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, modalEditUnlocked: true, draftFieldBlocks, customFacetDetailsMatchPreset, }), ), [ customFacetDetailsMatchPreset, draftFieldBlocks, pendingCardId, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, ], ); const handleCreateModalClose = useCallback(async () => { if ( !(await confirmDiscard( true, customizeSnapshotRef.current, pendingDraft, draftFieldBlocks, customizeSnapshotRef.current?.headerDraft ?? null, )) ) { return; } customizeSnapshotRef.current = null; const ephemeralId = pendingEphemeralDuplicateIdRef.current; if (ephemeralId) { pendingEphemeralDuplicateIdRef.current = null; replaceState((prev) => ({ ...prev, customMethodCardMetaById: omitIdFromStringRecord( prev.customMethodCardMetaById, ephemeralId, ), conflictManagementDetailsById: omitIdFromStringRecord( prev.conflictManagementDetailsById, ephemeralId, ), customMethodCardFieldBlocksById: omitIdFromStringRecord( prev.customMethodCardFieldBlocksById, ephemeralId, ), })); } setCreateModalOpen(false); setPendingCardId(null); setPendingDraft(null); setDraftFieldBlocks(null); }, [ confirmDiscard, draftFieldBlocks, pendingDraft, replaceState, ]); const handleRemoveSelectedFromModal = useCallback(async () => { if (!pendingCardId || !selectedIds.includes(pendingCardId)) { return; } markCreateFlowInteraction(); if ( !(await confirmDiscard( true, customizeSnapshotRef.current, pendingDraft, draftFieldBlocks, customizeSnapshotRef.current?.headerDraft ?? null, )) ) { return; } customizeSnapshotRef.current = null; updateState( removeMethodCardFromFacetSelection( state, "conflictManagement", pendingCardId, ), ); await handleCreateModalClose(); }, [ confirmDiscard, draftFieldBlocks, handleCreateModalClose, markCreateFlowInteraction, pendingDraft, pendingCardId, selectedIds, state, updateState, ]); const handleCustomize = useCallback(() => { markCreateFlowInteraction(); if (!pendingCardId) { return; } const method = methodById.get(pendingCardId); setWizardInitialValues( buildMethodCardWizardInitialValues({ cardId: pendingCardId, fallbackTitle: method?.label ?? cm.confirmModal.title, fallbackDescription: method?.supportText ?? cm.confirmModal.description, meta: state.customMethodCardMetaById, persistedBlocks: state.customMethodCardFieldBlocksById, draftFieldBlocks, facetPrefill: pendingDraft ? { group: "conflictManagement", draft: pendingDraft, headings: cm.sectionHeadings, } : undefined, }), ); setWizardCustomizeCardId(pendingCardId); setCreateModalOpen(false); setAddCustomWizardOpen(true); }, [ cm.confirmModal.description, cm.confirmModal.title, cm.sectionHeadings, draftFieldBlocks, markCreateFlowInteraction, methodById, pendingCardId, pendingDraft, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, ]); const handleDuplicateCustomCard = useCallback(() => { if ( !pendingCardId || !isCustomMethodCardId(pendingCardId, state.customMethodCardMetaById) ) { return; } markCreateFlowInteraction(); const newId = crypto.randomUUID(); const meta = state.customMethodCardMetaById![pendingCardId]!; const detailsClone = cloneMethodCardDetailsForDuplicate( pendingDraft, state.conflictManagementDetailsById?.[pendingCardId], () => conflictManagementPresetFor(newId), ); const blocksClone = structuredClone( draftFieldBlocks !== null ? draftFieldBlocks : cloneMethodCardBlocksForDuplicate( state.customMethodCardFieldBlocksById, pendingCardId, ), ); const suffix = modalKebabMenu.duplicateTitleSuffix; const priorEphemeral = pendingEphemeralDuplicateIdRef.current; const maps = forkMethodCardFacetMapsForDuplicate({ customMethodCardMetaById: state.customMethodCardMetaById, facetDetailsById: state.conflictManagementDetailsById, customMethodCardFieldBlocksById: state.customMethodCardFieldBlocksById, omitId: priorEphemeral, }); maps.customMethodCardMetaById[newId] = { label: duplicateMethodCardTitle(meta.label, suffix), supportText: meta.supportText, }; maps.facetDetailsById[newId] = detailsClone; maps.customMethodCardFieldBlocksById[newId] = blocksClone; updateState({ customMethodCardMetaById: maps.customMethodCardMetaById, conflictManagementDetailsById: maps.facetDetailsById, customMethodCardFieldBlocksById: maps.customMethodCardFieldBlocksById, }); pendingEphemeralDuplicateIdRef.current = newId; customizeSnapshotRef.current = null; setPendingCardId(newId); setPendingDraft(structuredClone(detailsClone)); setDraftFieldBlocks( blocksClone.length > 0 ? structuredClone(blocksClone) : null, ); }, [ draftFieldBlocks, markCreateFlowInteraction, modalKebabMenu.duplicateTitleSuffix, pendingCardId, pendingDraft, state.conflictManagementDetailsById, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, updateState, ]); const handleDuplicatePrefabCard = useCallback(() => { if ( !pendingCardId || isCustomMethodCardId(pendingCardId, state.customMethodCardMetaById) ) { return; } const method = methodById.get(pendingCardId); if (!method || !pendingDraft) { return; } markCreateFlowInteraction(); const newId = crypto.randomUUID(); const detailsClone = cloneMethodCardDetailsForDuplicate( pendingDraft, state.conflictManagementDetailsById?.[pendingCardId], () => conflictManagementPresetFor(newId), ); const blocksClone = structuredClone( draftFieldBlocks !== null ? draftFieldBlocks : cloneMethodCardBlocksForDuplicate( state.customMethodCardFieldBlocksById, pendingCardId, ), ); const suffix = modalKebabMenu.duplicateTitleSuffix; const priorEphemeral = pendingEphemeralDuplicateIdRef.current; const maps = forkMethodCardFacetMapsForDuplicate({ customMethodCardMetaById: state.customMethodCardMetaById, facetDetailsById: state.conflictManagementDetailsById, customMethodCardFieldBlocksById: state.customMethodCardFieldBlocksById, omitId: priorEphemeral, }); maps.customMethodCardMetaById[newId] = { label: duplicateMethodCardTitle(method.label, suffix), supportText: method.supportText, }; maps.facetDetailsById[newId] = detailsClone; maps.customMethodCardFieldBlocksById[newId] = blocksClone; updateState({ customMethodCardMetaById: maps.customMethodCardMetaById, conflictManagementDetailsById: maps.facetDetailsById, customMethodCardFieldBlocksById: maps.customMethodCardFieldBlocksById, }); pendingEphemeralDuplicateIdRef.current = newId; customizeSnapshotRef.current = null; setPendingCardId(newId); setPendingDraft(structuredClone(detailsClone)); setDraftFieldBlocks( blocksClone.length > 0 ? structuredClone(blocksClone) : null, ); }, [ draftFieldBlocks, markCreateFlowInteraction, methodById, modalKebabMenu.duplicateTitleSuffix, pendingCardId, pendingDraft, state.conflictManagementDetailsById, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, updateState, ]); const kebabMenuItems = useMemo( () => buildCustomRuleModalKebabMenu(modalKebabMenu, { showCustomize: true, onCustomize: handleCustomize, onDuplicate: (state.editingPublishedRuleId?.trim() ?? "") !== "" || !pendingCardId ? undefined : isCustomMethodCardId( pendingCardId, state.customMethodCardMetaById, ) ? handleDuplicateCustomCard : handleDuplicatePrefabCard, showRemove: isSelectedCardModal, onRemove: handleRemoveSelectedFromModal, }), [ handleCustomize, handleDuplicateCustomCard, handleDuplicatePrefabCard, handleRemoveSelectedFromModal, isSelectedCardModal, modalKebabMenu, pendingCardId, state.customMethodCardMetaById, state.editingPublishedRuleId, ], ); const modalConfig = pendingCardId ? (() => { const method = methodById.get(pendingCardId); const meta = state.customMethodCardMetaById?.[pendingCardId]; const saveLabel = modalKebabMenu.saveEdits; return { title: meta?.label ?? method?.label ?? cm.confirmModal.title, description: meta?.supportText ?? method?.supportText ?? cm.confirmModal.description, nextButtonText: isSelectedCardModal ? saveLabel : cm.addApproach.nextButtonText, }; })() : { title: cm.confirmModal.title, description: cm.confirmModal.description, nextButtonText: cm.confirmModal.nextButtonText, }; const handleCloseAddWizard = useCallback(() => { const resumeCardId = wizardCustomizeCardId; setAddCustomWizardOpen(false); setWizardCustomizeCardId(null); setWizardInitialValues(null); if (resumeCardId && pendingCardId === resumeCardId) { setCreateModalOpen(true); } }, [pendingCardId, wizardCustomizeCardId]); const handleFinalizeCustomCard = useCallback( ({ title, description, fieldBlocks, }: { title: string; description: string; fieldBlocks: CustomMethodCardFieldBlock[]; }) => { markCreateFlowInteraction(); const existingId = wizardCustomizeCardId; if (existingId) { updateState({ selectedConflictManagementIds: moveFacetSelectionIdToFront( selectedIds, existingId, ), customMethodCardMetaById: methodCardMetaWithCustomizeHeader( state.customMethodCardMetaById, existingId, { title, description }, ), ...(pendingDraft ? { conflictManagementDetailsById: { ...(state.conflictManagementDetailsById ?? {}), [existingId]: pendingDraft, }, } : {}), customMethodCardFieldBlocksById: { ...(state.customMethodCardFieldBlocksById ?? {}), [existingId]: fieldBlocks, }, }); if (pendingDraft) { customizeSnapshotRef.current = captureMethodCardCustomizeSnapshot( pendingDraft, fieldBlocks, { title, description }, ); } setDraftFieldBlocks(structuredClone(fieldBlocks)); setAddCustomWizardOpen(false); return; } const id = crypto.randomUUID(); updateState({ selectedConflictManagementIds: moveFacetSelectionIdToFront( selectedIds, id, ), customMethodCardMetaById: { ...(state.customMethodCardMetaById ?? {}), [id]: { label: title, supportText: description }, }, conflictManagementDetailsById: { ...(state.conflictManagementDetailsById ?? {}), [id]: conflictManagementPresetFor(id), }, customMethodCardFieldBlocksById: { ...(state.customMethodCardFieldBlocksById ?? {}), [id]: fieldBlocks, }, }); }, [ markCreateFlowInteraction, pendingDraft, selectedIds, state.conflictManagementDetailsById, state.customMethodCardFieldBlocksById, state.customMethodCardMetaById, updateState, wizardCustomizeCardId, ], ); const handleCreateModalPrimary = useCallback(() => { if (!pendingCardId) { void handleCreateModalClose(); return; } markCreateFlowInteraction(); const persistWizardBlocks = modalUsesWizardFieldBlocksBody && draftFieldBlocks !== null; if (selectedIds.includes(pendingCardId)) { if (persistWizardBlocks) { updateState({ customMethodCardFieldBlocksById: { ...(state.customMethodCardFieldBlocksById ?? {}), [pendingCardId]: structuredClone(draftFieldBlocks ?? []), }, }); } else if (pendingDraft) { updateState({ conflictManagementDetailsById: { ...(state.conflictManagementDetailsById ?? {}), [pendingCardId]: pendingDraft, }, }); } pendingEphemeralDuplicateIdRef.current = null; customizeSnapshotRef.current = null; void handleCreateModalClose(); return; } if (!pendingDraft) { void handleCreateModalClose(); return; } updateState({ selectedConflictManagementIds: moveFacetSelectionIdToFront( selectedIds, pendingCardId, ), conflictManagementDetailsById: { ...(state.conflictManagementDetailsById ?? {}), [pendingCardId]: pendingDraft, }, ...(persistWizardBlocks ? { customMethodCardFieldBlocksById: { ...(state.customMethodCardFieldBlocksById ?? {}), [pendingCardId]: structuredClone(draftFieldBlocks ?? []), }, } : {}), }); pendingEphemeralDuplicateIdRef.current = null; customizeSnapshotRef.current = null; void handleCreateModalClose(); }, [ draftFieldBlocks, handleCreateModalClose, markCreateFlowInteraction, modalUsesWizardFieldBlocksBody, pendingCardId, pendingDraft, selectedIds, state, updateState, ]); return ( <>
{recommendationsReady && ( { markCreateFlowInteraction(); setExpanded((prev) => !prev); }} hasMore={true} toggleLabel={cm.page.seeAllLink} compactRecommendedLimit={5} compactCardIds={compactCardIds} compactDesktopLayout="pyramidFive" headerLockupSize={mdUp ? "L" : "M"} /> )}
{pendingCardId && pendingDraft ? ( modalUsesWizardFieldBlocksBody ? ( setDraftFieldBlocks(next) } /> ) : ( ) ) : null}
uploadCreateFlowFile(file, "customMethodAttachment") } /> {confirmDialog} ); }