This commit is contained in:
parent
171fbf1927
commit
8bf3e3b705
40 changed files with 1443 additions and 1497 deletions
|
|
@ -1,73 +1,72 @@
|
|||
import type { FlooringPlan, Project, SkirtingPlan } from './types';
|
||||
import { flooringCuts, skirtingCuts } from './cut-list';
|
||||
import type {
|
||||
FlooringPlan,
|
||||
PieceKey,
|
||||
PlanFocus,
|
||||
PlanMaterial,
|
||||
Project,
|
||||
SkirtingPlan,
|
||||
} from './types';
|
||||
import type { CutData } from './cut-list';
|
||||
|
||||
export const pieceKey = (material: PlanMaterial, id: string): PieceKey => `${material}:${id}`;
|
||||
export const boardKey = (material: PlanMaterial, id: string) =>
|
||||
`${material === 'flooring' ? 'floor' : 'skirting'}:${id}`;
|
||||
|
||||
export interface BoardMatches {
|
||||
pieceIds: string[];
|
||||
pieceKeys: Set<PieceKey>;
|
||||
roomIds: string[];
|
||||
boardKeys: string[];
|
||||
boardKeys: Set<string>;
|
||||
materials: Set<PlanMaterial>;
|
||||
}
|
||||
|
||||
/** Codes match exactly; ordinary text searches room names. Qualified keys identify source boards. */
|
||||
/** User text searches cut codes or room names. Location is explicit, never encoded as a query. */
|
||||
export function findBoardMatches(
|
||||
project: Project,
|
||||
flooring: FlooringPlan,
|
||||
skirting: SkirtingPlan,
|
||||
query: string,
|
||||
focus: PlanFocus | null = null,
|
||||
): BoardMatches {
|
||||
const text = query.trim().toLowerCase().replace(/\s+/g, ' ');
|
||||
if (!text) return { pieceIds: [], roomIds: [], boardKeys: [] };
|
||||
const qualified = /^(floor|skirting)\s*:\s*([a-z]+\d+)$/.exec(text);
|
||||
const codes = text.split(/\s*[+,]\s*/);
|
||||
const isCode = codes.every((code) => /^[a-z]+\d+$/.test(code));
|
||||
const result: BoardMatches = {
|
||||
pieceKeys: new Set(),
|
||||
roomIds: [],
|
||||
boardKeys: new Set(),
|
||||
materials: new Set(),
|
||||
};
|
||||
if (!text && !focus) return result;
|
||||
const codes = new Set(text.split(/\s*[+,]\s*/));
|
||||
const matchingRooms = new Set(
|
||||
project.rooms
|
||||
.filter((room) => room.name.toLowerCase().replace(/\s+/g, ' ').includes(text))
|
||||
.map((room) => room.id),
|
||||
);
|
||||
const pieceIds: string[] = [];
|
||||
const roomIds = new Set<string>();
|
||||
const boardKeys = new Set<string>();
|
||||
const materials = [
|
||||
{ key: 'floor', pieces: flooring.pieces },
|
||||
{ key: 'skirting', pieces: skirting.pieces },
|
||||
];
|
||||
for (const material of materials) {
|
||||
for (const piece of material.pieces) {
|
||||
const matches = qualified
|
||||
? material.key === qualified[1] && piece.stockId.toLowerCase() === qualified[2]
|
||||
: isCode
|
||||
? codes.includes(piece.id.toLowerCase()) ||
|
||||
(codes.length === 1 && codes.includes(piece.stockId.toLowerCase()))
|
||||
: matchingRooms.has(piece.roomId);
|
||||
for (const [material, pieces] of [
|
||||
['flooring', flooring.pieces],
|
||||
['skirting', skirting.pieces],
|
||||
] as const) {
|
||||
for (const piece of pieces) {
|
||||
const matches = focus
|
||||
? material === focus.material &&
|
||||
(focus.kind === 'board' ? piece.stockId : piece.id) === focus.id
|
||||
: codes.has(piece.id.toLowerCase()) || matchingRooms.has(piece.roomId);
|
||||
if (!matches) continue;
|
||||
pieceIds.push(piece.id);
|
||||
result.pieceKeys.add(pieceKey(material, piece.id));
|
||||
roomIds.add(piece.roomId);
|
||||
boardKeys.add(`${material.key}:${piece.stockId}`);
|
||||
result.boardKeys.add(boardKey(material, piece.stockId));
|
||||
result.materials.add(material);
|
||||
}
|
||||
}
|
||||
return {
|
||||
pieceIds,
|
||||
roomIds: project.rooms.filter((room) => roomIds.has(room.id)).map((room) => room.id),
|
||||
boardKeys: [...boardKeys],
|
||||
};
|
||||
result.roomIds = project.rooms.filter((room) => roomIds.has(room.id)).map((room) => room.id);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Source IDs are bookkeeping; show the cut codes when locating a whole source board. */
|
||||
export function boardSearchLabel(
|
||||
project: Project,
|
||||
flooring: FlooringPlan,
|
||||
skirting: SkirtingPlan,
|
||||
query: string,
|
||||
): string {
|
||||
const qualified = /^(floor|skirting)\s*:\s*([a-z]+\d+)$/i.exec(query.trim());
|
||||
if (!qualified) return query;
|
||||
const matches = new Set(findBoardMatches(project, flooring, skirting, query).pieceIds);
|
||||
const codes = [...flooringCuts(flooring), ...skirtingCuts(skirting)]
|
||||
.filter((piece) => matches.has(piece.id))
|
||||
.map((piece) => piece.id);
|
||||
return codes.length
|
||||
? codes.join(' + ')
|
||||
: qualified[1].toLowerCase() === 'floor'
|
||||
? 'Uncut floorboard'
|
||||
: 'Uncut skirting board';
|
||||
export function focusLabel(focus: PlanFocus, cuts: CutData): string {
|
||||
if (focus.kind === 'piece') return focus.id;
|
||||
const group = (focus.material === 'flooring' ? cuts.floorGroups : cuts.skirtGroups).get(focus.id);
|
||||
return (
|
||||
group?.map((piece) => piece.id).join(' + ') ||
|
||||
(focus.material === 'flooring' ? 'Uncut floorboard' : 'Uncut skirting board')
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue