Show the whole Rule on one review card, with quiet flags for missing cards and Continue once each question has one.
This commit is contained in:
1 parent
cc878e3c19
commit
6e2e1915ae
30 files changed
+571
-218
No files matched your search
@@ -50,6 +50,56 @@ export type FinalReviewCategoryNames = {
|
||||
conflict: string;
|
||||
};
|
||||
|
||||
const CANONICAL_FACET_ORDER: readonly TemplateFacetGroupKey[] = [
|
||||
"coreValues",
|
||||
"communication",
|
||||
"membership",
|
||||
"decisionApproaches",
|
||||
"conflictManagement",
|
||||
] as const;
|
||||
|
||||
function nameForGroup(
|
||||
groupKey: TemplateFacetGroupKey,
|
||||
names: FinalReviewCategoryNames,
|
||||
): string {
|
||||
switch (groupKey) {
|
||||
case "coreValues":
|
||||
return names.values;
|
||||
case "communication":
|
||||
return names.communication;
|
||||
case "membership":
|
||||
return names.membership;
|
||||
case "decisionApproaches":
|
||||
return names.decisions;
|
||||
case "conflictManagement":
|
||||
return names.conflict;
|
||||
}
|
||||
}
|
||||
|
||||
/** Always five section rows, in values-first order, including empty ones. */
|
||||
export function ensureCanonicalFinalReviewRows(
|
||||
rows: readonly FinalReviewCategoryRowDetailed[],
|
||||
names: FinalReviewCategoryNames,
|
||||
): FinalReviewCategoryRowDetailed[] {
|
||||
const byGroup = new Map<TemplateFacetGroupKey, FinalReviewCategoryRowDetailed>();
|
||||
for (const row of rows) {
|
||||
if (row.groupKey == null) continue;
|
||||
byGroup.set(row.groupKey, {
|
||||
...row,
|
||||
name: nameForGroup(row.groupKey, names),
|
||||
});
|
||||
}
|
||||
return CANONICAL_FACET_ORDER.map((groupKey) => {
|
||||
const existing = byGroup.get(groupKey);
|
||||
if (existing) return existing;
|
||||
return {
|
||||
name: nameForGroup(groupKey, names),
|
||||
groupKey,
|
||||
entries: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
type MethodPreset = { id: string; label: string };
|
||||
|
||||
/**
|
||||
@@ -230,7 +280,7 @@ export function buildFinalReviewCategoryRowsDetailed(
|
||||
if (entries.length === 0) continue;
|
||||
rows.push({ name: s.categoryName, groupKey, entries });
|
||||
}
|
||||
return rows;
|
||||
return ensureCanonicalFinalReviewRows(rows, names);
|
||||
}
|
||||
|
||||
const rows: FinalReviewCategoryRowDetailed[] = [
|
||||
@@ -276,7 +326,7 @@ export function buildFinalReviewCategoryRowsDetailed(
|
||||
),
|
||||
},
|
||||
];
|
||||
return rows.filter((r) => r.entries.length > 0);
|
||||
return ensureCanonicalFinalReviewRows(rows, names);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,9 +343,8 @@ export function buildFinalReviewCategoryRowsDetailed(
|
||||
* against the curated message `methods[]` list to get the display labels,
|
||||
* matching what the user saw as chips in-flow.
|
||||
*
|
||||
* Empty categories are filtered out so the review card doesn't render headings
|
||||
* with no chips. If nothing in state resolves to any chip, the caller should
|
||||
* fall back to the demo categories shipped in `finalReview.json`.
|
||||
* Empty categories stay on the card so add chips and flags can show. The five
|
||||
* named rows always render, in values-first order.
|
||||
*/
|
||||
export function buildFinalReviewCategoriesFromState(
|
||||
state: CreateFlowState,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/** One or two letters from a community name for the Rule card mark. */
|
||||
export function communityInitialsFromTitle(title: string): string {
|
||||
const parts = title.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) return "";
|
||||
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
||||
return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type {
|
||||
CommunicationMethodDetailEntry,
|
||||
ConflictManagementDetailEntry,
|
||||
CreateFlowState,
|
||||
DecisionApproachDetailEntry,
|
||||
MembershipMethodDetailEntry,
|
||||
} from "../../app/(app)/create/types";
|
||||
import type { TemplateFacetGroupKey } from "./templateReviewMapping";
|
||||
|
||||
function missingText(...values: readonly (string | undefined)[]): boolean {
|
||||
return values.some((value) => (value ?? "").trim().length === 0);
|
||||
}
|
||||
|
||||
function communicationMissingWords(
|
||||
saved: CommunicationMethodDetailEntry | undefined,
|
||||
): boolean {
|
||||
if (!saved) return true;
|
||||
return missingText(
|
||||
saved.corePrinciple,
|
||||
saved.logisticsAdmin,
|
||||
saved.codeOfConduct,
|
||||
);
|
||||
}
|
||||
|
||||
function membershipMissingWords(
|
||||
saved: MembershipMethodDetailEntry | undefined,
|
||||
): boolean {
|
||||
if (!saved) return true;
|
||||
return missingText(
|
||||
saved.eligibility,
|
||||
saved.joiningProcess,
|
||||
saved.expectations,
|
||||
);
|
||||
}
|
||||
|
||||
function decisionMissingWords(
|
||||
saved: DecisionApproachDetailEntry | undefined,
|
||||
): boolean {
|
||||
if (!saved) return true;
|
||||
return missingText(
|
||||
saved.corePrinciple,
|
||||
saved.stepByStepInstructions,
|
||||
saved.objectionsDeadlocks,
|
||||
);
|
||||
}
|
||||
|
||||
function conflictMissingWords(
|
||||
saved: ConflictManagementDetailEntry | undefined,
|
||||
): boolean {
|
||||
if (!saved) return true;
|
||||
return missingText(
|
||||
saved.corePrinciple,
|
||||
saved.processProtocol,
|
||||
saved.restorationFallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A review chip shows a warning when its value is missing meaning or signals,
|
||||
* or a card is missing its words.
|
||||
*/
|
||||
export function finalReviewChipHasWarning(
|
||||
state: CreateFlowState,
|
||||
groupKey: TemplateFacetGroupKey | null,
|
||||
overrideKey: string | null,
|
||||
): boolean {
|
||||
if (!groupKey || !overrideKey) return true;
|
||||
switch (groupKey) {
|
||||
case "coreValues": {
|
||||
const saved = state.coreValueDetailsByChipId?.[overrideKey];
|
||||
return missingText(saved?.meaning, saved?.signals);
|
||||
}
|
||||
case "communication":
|
||||
return communicationMissingWords(
|
||||
state.communicationMethodDetailsById?.[overrideKey],
|
||||
);
|
||||
case "membership":
|
||||
return membershipMissingWords(
|
||||
state.membershipMethodDetailsById?.[overrideKey],
|
||||
);
|
||||
case "decisionApproaches":
|
||||
return decisionMissingWords(
|
||||
state.decisionApproachDetailsById?.[overrideKey],
|
||||
);
|
||||
case "conflictManagement":
|
||||
return conflictMissingWords(
|
||||
state.conflictManagementDetailsById?.[overrideKey],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user