import { MapPin, X } from 'lucide-react'; import type { FlooringPlan, Project, SkirtingPlan } from '../lib/types'; import { cutMm, mm } from '../lib/format'; import { flooringCuts, groupFlooringCuts, groupSkirtingCuts, skirtingCuts, skirtingEndLabel, uncutFlooring, uncutSkirting, } from '../lib/cut-list'; import { boardSearchLabel, findBoardMatches } from '../lib/board-tracking'; import { PERSISTENCE_LIMITS } from '../lib/limits'; import { FlooringPreview, SkirtingPreview } from './CutPreview'; function BoardHeader({ label, size, done, toggle, locate, }: { label: string; size: string; done: boolean; toggle: () => void; locate: () => void; }) { return (

{label.charAt(0).toUpperCase() + label.slice(1)} {size}

); } interface Props { project: Project; flooring: FlooringPlan; skirting: SkirtingPlan; select: (id: string | null, roomId?: string) => void; completedBoards: string[]; toggleBoard: (key: string) => void; search: string; setSearch: (query: string) => void; locateBoard: (key: string) => void; locatePiece: (id: string, roomId: string) => void; } export function CutList({ project, flooring, skirting, select, completedBoards, toggleBoard, search, setSearch, locateBoard, locatePiece, }: Props) { const floorCuts = flooringCuts(flooring), skirtCuts = skirtingCuts(skirting); const wholeFloor = uncutFlooring(flooring, project.flooring), wholeSkirt = uncutSkirting(skirting); const floorGroups = new Map( groupFlooringCuts(flooring).map((group) => [group[0].stockId, group]), ); const skirtGroups = new Map( groupSkirtingCuts(skirting).map((group) => [group[0].stockId, group]), ); const searching = search.trim().length > 0; const matches = findBoardMatches(project, flooring, skirting, search); const matchedBoards = new Set(matches.boardKeys), matchedPieces = new Set(matches.pieceIds); const completed = new Set(completedBoards); // Keep every cut from a matching source together, even when only one piece or room matched. const floorStocks = flooring.stocks.filter((stock) => searching ? matchedBoards.has(`floor:${stock.id}`) : floorGroups.has(stock.id), ); const skirtStocks = skirting.stocks.filter((stock) => searching ? matchedBoards.has(`skirting:${stock.id}`) : skirtGroups.has(stock.id), ); const roomName = (id: string) => project.rooms.find((room) => room.id === id)?.name ?? id; return (

Cut plan

Each group comes from one board. Select a piece for its cutting diagram.

setSearch(event.target.value)} placeholder="F12, A2, or a room name" autoComplete="off" /> {search.length > 0 && ( )}
{searching && (

{matches.boardKeys.length ? `${matches.boardKeys.length} matching board${matches.boardKeys.length === 1 ? '' : 's'}` : 'No matching boards or cuts.'}

)}

Flooring cuts {floorCuts.length}

Buy {flooring.boardCount} boards · {wholeFloor.wholeBoards} laid uncut {wholeFloor.readyOffcuts > 0 && ` · ${wholeFloor.readyOffcuts} offcuts need no further cuts`}

{floorStocks.map((stock) => { const key = `floor:${stock.id}`, done = completed.has(key), group = floorGroups.get(stock.id) ?? [], label = group.length ? `cuts ${group.map((piece) => piece.id).join(' + ')}` : 'uncut floorboard'; return (
toggleBoard(key)} locate={() => locateBoard(key)} /> {group.length ? (
    {group.map((piece) => (
  • {cutMm(piece.length, piece.blankLength)} ×{' '} {cutMm(piece.width, piece.blankWidth)} mm

    {roomName(piece.roomId)} · Row {piece.row}

    {piece.reused && (

    {piece.cut ? 'Cut from the remaining offcut' : 'Use the offcut as it is'}

    )}
  • ))}
) : (

Installed uncut ·{' '} {flooring.pieces .filter((piece) => piece.stockId === stock.id) .map((piece) => roomName(piece.roomId)) .join(', ')}

)}
); })} {!floorStocks.length && (

{searching ? 'No matching floorboards.' : flooring.pieces.length ? 'No flooring cuts needed.' : 'Complete the outline and flooring measurements to calculate cuts.'}

)}

Skirting cuts {skirtCuts.length}

Buy {skirting.stockCount} lengths ·{' '} {wholeSkirt.reduce((sum, group) => sum + group.count, 0)} installed uncut

{skirtStocks.map((stock) => { const key = `skirting:${stock.id}`, done = completed.has(key), group = skirtGroups.get(stock.id) ?? [], label = group.length ? `cuts ${group.map((piece) => piece.id).join(' + ')}` : 'uncut skirting board'; return (
toggleBoard(key)} locate={() => locateBoard(key)} /> {group.length ? (
    {group.map((piece) => (
  • {cutMm(piece.length, piece.blankLength)} mm{' '} long point

    {roomName(piece.roomId)} · Wall {piece.wallIndex + 1}

    Left: {skirtingEndLabel(piece, 'left')}
    Right: {skirtingEndLabel(piece, 'right')}

  • ))}
) : (

Installed uncut ·{' '} {skirting.pieces .filter((piece) => piece.stockId === stock.id) .map((piece) => roomName(piece.roomId)) .join(', ')}

)}
); })} {!skirtStocks.length && (

{searching ? 'No matching skirting boards.' : skirting.pieces.length ? 'No skirting cuts needed.' : 'Complete the outline and skirting measurements to calculate cuts.'}

)}
); }