This commit is contained in:
Andras Schmelczer 2026-09-11 16:58:58 +01:00
commit 62a385e4c9
33 changed files with 2649 additions and 809 deletions

73
src/lib/board-tracking.ts Normal file
View file

@ -0,0 +1,73 @@
import type { FlooringPlan, Project, SkirtingPlan } from './types';
import { flooringCuts, skirtingCuts } from './cut-list';
export interface BoardMatches {
pieceIds: string[];
roomIds: string[];
boardKeys: string[];
}
/** Codes match exactly; ordinary text searches room names. Qualified keys identify source boards. */
export function findBoardMatches(
project: Project,
flooring: FlooringPlan,
skirting: SkirtingPlan,
query: string,
): 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 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);
if (!matches) continue;
pieceIds.push(piece.id);
roomIds.add(piece.roomId);
boardKeys.add(`${material.key}:${piece.stockId}`);
}
}
return {
pieceIds,
roomIds: project.rooms.filter((room) => roomIds.has(room.id)).map((room) => room.id),
boardKeys: [...boardKeys],
};
}
/** 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';
}