Files
community-rule/app/(app)/create/hooks/useMethodCardDeckOrdering.ts
T

136 lines
4.0 KiB
TypeScript

"use client";
import { useMemo } from "react";
import { mergeCompactCardIdsWithPinnedSelected } from "../../../../lib/create/methodCardDisplayOrder";
import { formatRecommendationWhyLine } from "../../../../lib/create/formatRecommendationWhyLine";
import {
deriveCompactCards,
groupAnswersForRecommendations,
type RecommendationSection,
} from "./useFacetRecommendations";
import { deckData } from "../../../../lib/create/deckRecommend";
import { useCreateFlow } from "../context/CreateFlowContext";
import { useMessages } from "../../../contexts/MessagesContext";
type MethodEntry = { id: string; label: string; supportText: string };
function orderByIds<T extends { id: string }>(
methods: readonly T[],
orderedIds: readonly string[],
): T[] {
const byId = new Map(methods.map((method) => [method.id, method]));
const ordered = orderedIds.flatMap((id) => {
const method = byId.get(id);
return method ? [method] : [];
});
const seen = new Set(ordered.map((method) => method.id));
return [...ordered, ...methods.filter((method) => !seen.has(method.id))];
}
/**
* Deck order and the compact stack come from the job rule. Compact shows
* at most seven recommended cards. Selected cards still pin into the stack.
*/
export function useMethodCardDeckOrdering(
section: RecommendationSection,
methods: readonly MethodEntry[],
selectedIds: readonly string[],
) {
const { state } = useCreateFlow();
const copy = useMessages().create.customRule.recommendations;
const compactLimit = deckData(section).config.displayLimit;
const answers = useMemo(
() => groupAnswersForRecommendations(state),
[state],
);
const deck = useMemo(
() => deriveCompactCards(section, answers, compactLimit),
[section, answers, compactLimit],
);
const rankedMethods = useMemo(
() => orderByIds(methods, deck.orderedIds),
[methods, deck.orderedIds],
);
const selectionShowcaseActive = selectedIds.length > 0;
const { recommendedIds } = deck;
const compactCardIds = useMemo(
() =>
mergeCompactCardIdsWithPinnedSelected(
rankedMethods.map((method) => method.id),
deck.compactCardIds,
selectedIds,
selectionShowcaseActive,
compactLimit,
),
[
rankedMethods,
deck.compactCardIds,
selectedIds,
selectionShowcaseActive,
compactLimit,
],
);
const compactIdSet = useMemo(
() => new Set(compactCardIds),
[compactCardIds],
);
const sampleCards = useMemo(
() =>
rankedMethods.map((entry) => {
const recommendation = deck.recommendedById.get(entry.id);
const whyLine =
recommendation && recommendation.tag !== "everyRuleHas"
? formatRecommendationWhyLine(recommendation.reason, answers, copy)
: "";
const alternativeId = recommendation?.alternative;
const alternativeMethod = alternativeId
? rankedMethods.find((method) => method.id === alternativeId) ??
methods.find((method) => method.id === alternativeId)
: undefined;
return {
id: entry.id,
label: entry.label,
supportText: entry.supportText,
compactSupportText:
whyLine && compactIdSet.has(entry.id) ? whyLine : undefined,
recommended: recommendedIds.has(entry.id),
alternative:
alternativeMethod && compactIdSet.has(entry.id)
? { id: alternativeMethod.id, label: alternativeMethod.label }
: undefined,
};
}),
[
answers,
compactIdSet,
copy,
deck.recommendedById,
methods,
rankedMethods,
recommendedIds,
],
);
const methodById = useMemo(
() => new Map(rankedMethods.map((entry) => [entry.id, entry])),
[rankedMethods],
);
return {
rankedMethods,
displayMethods: rankedMethods,
compactCardIds,
recommendedIds,
sampleCards,
methodById,
showDeckLine: !deck.fallback,
compactLimit,
recommendationsReady: true,
};
}