import { nextId, rectangle } from './defaults'; import { PROJECT_LIMITS } from './limits'; import type { FlooringSettings, PlanFocus, PlanMaterial, PlannerSection, Project, Room, SkirtingSettings, UrlState, } from './types'; export type ProjectAction = | { type: 'room'; id: string; change: (room: Room) => Room } | { type: 'flooring'; patch: Partial } | { type: 'skirting'; patch: Partial } | { type: 'tolerance'; value: number } | { type: 'addRoom' } | { type: 'removeRoom'; id: string } | { type: 'visitRoom'; id: string } | { type: 'material'; material: PlanMaterial } | { type: 'select'; material: PlanMaterial; id: string; roomId?: string } | { type: 'closePreview' } | { type: 'search'; text: string; roomId: string; material: PlanMaterial } | { type: 'focus'; focus: PlanFocus; roomId: string } | { type: 'toggleBoard'; key: string } | { type: 'toggleSection'; section: PlannerSection }; const toggle = (items: T[], value: T) => items.includes(value) ? items.filter((item) => item !== value) : [...items, value]; /** Allocation changes invalidate progress and locations together. Renames leave them intact. */ function changedPlan(state: UrlState, project: Project): UrlState { return { project: { ...project, completedBoards: [] }, view: { ...state.view, selectedPiece: null, search: '', focus: null }, }; } export function reduceProject(state: UrlState, action: ProjectAction): UrlState { const { project, view } = state; switch (action.type) { case 'room': { const room = project.rooms.find((room) => room.id === action.id); if (!room) return state; const next = action.change(room); if (next === room) return state; const updated = { ...project, rooms: project.rooms.map((item) => (item === room ? next : item)), }; return next.walls === room.walls && next.direction === room.direction ? { ...state, project: updated } : changedPlan(state, updated); } case 'flooring': case 'skirting': { if ( Object.entries(action.patch).every( ([key, value]) => Reflect.get(project[action.type], key) === value, ) ) return state; return changedPlan(state, { ...project, [action.type]: { ...project[action.type], ...action.patch }, }); } case 'tolerance': return action.value === project.tolerance ? state : changedPlan(state, { ...project, tolerance: action.value }); case 'addRoom': { if (project.rooms.length >= PROJECT_LIMITS.rooms) return state; const id = nextId( 'room', project.rooms.map((room) => room.id), ); const next = changedPlan(state, { ...project, rooms: [...project.rooms, rectangle(id, `Room ${project.rooms.length + 1}`)], }); return { ...next, view: { ...next.view, roomId: id, collapsedSections: view.collapsedSections.filter((section) => section !== 'measurements'), }, }; } case 'removeRoom': { if (project.rooms.length <= 1) return state; const rooms = project.rooms.filter((room) => room.id !== action.id); if (rooms.length === project.rooms.length) return state; const next = changedPlan(state, { ...project, rooms }); return { ...next, view: { ...next.view, roomId: rooms.some((room) => room.id === view.roomId) ? view.roomId : rooms[0].id, }, }; } case 'visitRoom': return project.rooms.some((room) => room.id === action.id) ? { ...state, view: { ...view, roomId: action.id, selectedPiece: null } } : state; case 'material': return { ...state, view: { ...view, material: action.material, selectedPiece: null, search: '', focus: null }, }; case 'select': return { ...state, view: { ...view, selectedPiece: `${action.material}:${action.id}`, material: action.material, roomId: action.roomId ?? view.roomId, }, }; case 'closePreview': return { ...state, view: { ...view, selectedPiece: null } }; case 'search': return { ...state, view: { ...view, search: action.text, roomId: action.roomId, material: action.material, focus: null, selectedPiece: null, }, }; case 'focus': return { ...state, view: { ...view, focus: action.focus, material: action.focus.material, roomId: action.roomId, search: '', selectedPiece: null, }, }; case 'toggleBoard': return { ...state, project: { ...project, completedBoards: toggle(project.completedBoards, action.key) }, }; case 'toggleSection': return { ...state, view: { ...view, collapsedSections: toggle(view.collapsedSections, action.section) }, }; } }