178 lines
5.8 KiB
TypeScript
178 lines
5.8 KiB
TypeScript
import type { DecisionApproachDetailEntry } from "../../app/(app)/create/types";
|
|
import decisionApproachesMessages from "../../messages/en/create/customRule/decisionApproaches.json";
|
|
|
|
export type DecisionApproachKeyResourceItem = {
|
|
id: string;
|
|
label: string;
|
|
};
|
|
|
|
function uniquePreserveOrder(values: readonly string[]): string[] {
|
|
const seen = new Set<string>();
|
|
const out: string[] = [];
|
|
for (const value of values) {
|
|
if (seen.has(value)) continue;
|
|
seen.add(value);
|
|
out.push(value);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function isKeyResourceItem(value: unknown): value is DecisionApproachKeyResourceItem {
|
|
if (!value || typeof value !== "object") return false;
|
|
const row = value as { id?: unknown; label?: unknown };
|
|
return typeof row.id === "string" && typeof row.label === "string";
|
|
}
|
|
|
|
/**
|
|
* Sidebar “key resource” checkboxes on the decision-approaches step.
|
|
* These labels are also offered as Applicable Scope chips on every approach.
|
|
*/
|
|
export function decisionApproachKeyResourceItems(): DecisionApproachKeyResourceItem[] {
|
|
const items = (
|
|
decisionApproachesMessages as {
|
|
messageBox?: { items?: unknown };
|
|
}
|
|
).messageBox?.items;
|
|
if (!Array.isArray(items)) return [];
|
|
return items.filter(isKeyResourceItem);
|
|
}
|
|
|
|
export function decisionApproachKeyResourceLabels(): string[] {
|
|
return decisionApproachKeyResourceItems().map((item) => item.label);
|
|
}
|
|
|
|
export function selectedKeyResourceLabelsFromCheckedIds(
|
|
checkedIds: readonly string[],
|
|
): string[] {
|
|
const idSet = new Set(checkedIds);
|
|
return decisionApproachKeyResourceItems()
|
|
.filter((item) => idSet.has(item.id))
|
|
.map((item) => item.label);
|
|
}
|
|
|
|
export function decisionApproachKeyResourceIdsFromLabels(
|
|
labels: readonly string[],
|
|
): string[] {
|
|
const labelSet = new Set(labels);
|
|
return decisionApproachKeyResourceItems()
|
|
.filter((item) => labelSet.has(item.label))
|
|
.map((item) => item.id);
|
|
}
|
|
|
|
/**
|
|
* Sidebar checkboxes follow the open approach while its modal is up.
|
|
* With no modal they show key resources assigned to any selected approach,
|
|
* plus extra reminder checks that are not painted onto chips.
|
|
*/
|
|
export function decisionApproachKeyResourceCheckboxIds(params: {
|
|
detailsById: Record<string, DecisionApproachDetailEntry> | undefined;
|
|
selectedApproachIds: readonly string[];
|
|
reminderIds?: readonly string[];
|
|
openDraft?: DecisionApproachDetailEntry | null;
|
|
}): string[] {
|
|
if (params.openDraft) {
|
|
return decisionApproachKeyResourceIdsFromLabels(
|
|
params.openDraft.selectedApplicableScope,
|
|
);
|
|
}
|
|
const assignedLabels: string[] = [];
|
|
for (const id of params.selectedApproachIds) {
|
|
const entry = params.detailsById?.[id];
|
|
if (!entry) continue;
|
|
assignedLabels.push(...entry.selectedApplicableScope);
|
|
}
|
|
return uniquePreserveOrder([
|
|
...decisionApproachKeyResourceIdsFromLabels(assignedLabels),
|
|
...(params.reminderIds ?? []),
|
|
]);
|
|
}
|
|
|
|
export function stringArraysEqual(
|
|
a: readonly string[],
|
|
b: readonly string[],
|
|
): boolean {
|
|
if (a.length !== b.length) return false;
|
|
return a.every((value, index) => value === b[index]);
|
|
}
|
|
|
|
/**
|
|
* Offer the four sidebar labels as Applicable Scope chips without persisting
|
|
* them onto `applicableScope` (unchecked keys must not publish as defaults).
|
|
*/
|
|
export function withDecisionApproachKeyResourceScopes(
|
|
scopes: readonly string[],
|
|
): string[] {
|
|
return uniquePreserveOrder([
|
|
...scopes,
|
|
...decisionApproachKeyResourceLabels(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Keep non-key chip selections, then select the key-resource labels whose
|
|
* sidebar boxes are checked.
|
|
*/
|
|
export function applyDecisionApproachKeyResources(
|
|
entry: DecisionApproachDetailEntry,
|
|
selectedKeyResourceLabels: readonly string[],
|
|
): DecisionApproachDetailEntry {
|
|
const keyLabels = decisionApproachKeyResourceLabels();
|
|
const keySet = new Set(keyLabels);
|
|
const selectedSet = new Set(selectedKeyResourceLabels);
|
|
return {
|
|
...entry,
|
|
selectedApplicableScope: uniquePreserveOrder([
|
|
...entry.selectedApplicableScope.filter((scope) => !keySet.has(scope)),
|
|
...keyLabels.filter((label) => selectedSet.has(label)),
|
|
]),
|
|
};
|
|
}
|
|
|
|
export function withoutDecisionApproachKeyResourceLabels(
|
|
scopes: readonly string[],
|
|
): string[] {
|
|
const keySet = new Set(decisionApproachKeyResourceLabels());
|
|
return scopes.filter((scope) => !keySet.has(scope));
|
|
}
|
|
|
|
/**
|
|
* Publish selected original scopes (or all originals when none are picked)
|
|
* plus any checked key-resource labels. Checking a key resource must not drop
|
|
* unselected default scopes.
|
|
*/
|
|
export function decisionApproachScopeForPublish(
|
|
applicableScope: readonly unknown[],
|
|
selectedApplicableScope: readonly unknown[],
|
|
): string[] {
|
|
const asStrings = (values: readonly unknown[]): string[] =>
|
|
values.filter((value): value is string => typeof value === "string");
|
|
const keySet = new Set(decisionApproachKeyResourceLabels());
|
|
const original = withoutDecisionApproachKeyResourceLabels(
|
|
asStrings(applicableScope),
|
|
);
|
|
const selected = asStrings(selectedApplicableScope);
|
|
const selectedOther = withoutDecisionApproachKeyResourceLabels(selected);
|
|
const selectedKeys = selected.filter((scope) => keySet.has(scope));
|
|
return uniquePreserveOrder([
|
|
...(selectedOther.length > 0 ? selectedOther : original),
|
|
...selectedKeys,
|
|
]);
|
|
}
|
|
|
|
export function syncDecisionApproachKeyResourceDetails(
|
|
detailsById: Record<string, DecisionApproachDetailEntry> | undefined,
|
|
extraIds: readonly string[],
|
|
selectedKeyResourceLabels: readonly string[],
|
|
seed: (_id: string) => DecisionApproachDetailEntry,
|
|
): Record<string, DecisionApproachDetailEntry> {
|
|
const next: Record<string, DecisionApproachDetailEntry> = {};
|
|
const ids = new Set<string>([...Object.keys(detailsById ?? {}), ...extraIds]);
|
|
for (const id of ids) {
|
|
next[id] = applyDecisionApproachKeyResources(
|
|
detailsById?.[id] ?? seed(id),
|
|
selectedKeyResourceLabels,
|
|
);
|
|
}
|
|
return next;
|
|
}
|