import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit"; import { attributesAsList, Id, Product } from "@/lib/product"; import { dimensions_t, length_t } from "@/lib/dimensions"; import uuid from "react-native-uuid"; import { AppStore, RootState } from "@/app/store"; import { Length } from "convert"; export const DEFAULT_UNITS: Length = "ft"; export type PlywoodCalculationState = { product?: Id | null; units: Length; }; export type CarpetCalculationState = { product?: Id | null; length: length_t; inner_d: length_t; outer_d: length_t; }; export type AppStoreState = { products: Product[]; calculations?: { plywood: PlywoodCalculationState; carpet: CarpetCalculationState; }; }; export const DEFAULT_PRELOADED_STATE = { products: [] as Product[], calculations: { plywood: { product: undefined, units: DEFAULT_UNITS, }, carpet: { product: undefined, length: { l: 0, u: DEFAULT_UNITS, }, inner_d: { l: 0, u: DEFAULT_UNITS, }, outer_d: { l: 0, u: DEFAULT_UNITS, }, }, }, } as AppStoreState; const initialState: AppStoreState = DEFAULT_PRELOADED_STATE; export type UpdateAttribute = { product_id: Id; attributeKey: string; attributeValue: any; }; export type UpdateAttributeKey = { product_id: Id; oldKey: string; newKey: string; }; export type AddAttribute = { product_id: Id; }; const cp = (obj: any) => JSON.parse(JSON.stringify(obj)); const productsState = createSlice({ name: "products-slice", initialState, reducers: { setPlywoodCalculation( state, action: PayloadAction ) { if (!state) { return initialState; } const newCalc = action.payload; state.calculations.plywood = newCalc; return state; }, setCarpetCalculation(state, action: PayloadAction) { if (!state) { return initialState; } const newCalc = action.payload; state.calculations.carpet = newCalc; return state; }, createProduct(state, action: PayloadAction) { if (!state) { return initialState; } const product = action.payload; if (!product.id) product.id = uuid.v4().toString(); state.products = [...state.products, action.payload]; return state; }, deleteProduct(state, action: PayloadAction) { if (!state) return initialState; return { ...state, products: [ ...state.products.filter((prod) => { return prod.id?.valueOf() !== action.payload.valueOf(); }), ], }; }, updateAttribute(state, action: PayloadAction) { const { product_id, attributeKey, attributeValue } = action.payload; if (!state) return initialState; return { ...state, products: state.products.map((prod) => { if (prod.id !== product_id) return prod; const attributes = cp(prod.attributes); attributes[attributeKey] = attributeValue; return { ...prod, attributes, }; }), }; }, changeKey(state, action: PayloadAction) { if (!state) return initialState; const { product_id, oldKey, newKey } = action.payload; return { ...state, products: state.products.map((prod) => { if (prod.id !== product_id) return prod; const attributes = cp(prod.attributes); attributes[newKey] = attributes[oldKey]; delete attributes[oldKey]; attributes.id = prod.id; return { ...prod, attributes, }; }), }; }, addAttribute(state, action: PayloadAction) { if (!state) return initialState; const product_id = action.payload; state.products = state.products.map((prod) => { if (prod.id !== product_id) return prod; const i = ( Object.keys(prod.attributes || {}).filter((k) => k.match(/attribute [\d]+/) ) || [] ).length; const newAttribute = `attribute ${i + 1}`; return { ...prod, attributes: { ...prod.attributes, [newAttribute]: `value`, }, }; }); return state; }, deleteAttribute( state, action: PayloadAction<{ product_id: Id; attribute: string }> ) { if (!state) return initialState; const { product_id, attribute } = action.payload; return { ...state, products: state.products.map((prod) => { if (prod.id !== product_id) return prod; const attributes = Object.fromEntries( Object.entries(prod).filter(([k, v]) => k !== attribute) ); return { ...prod, attributes, }; }), }; }, updatePrice( state, action: PayloadAction<{ product_id: Id; pricePerUnit: number }> ) { if (!state) return initialState; const { product_id, pricePerUnit } = action.payload; state.products = state.products.map((prod) => { if (prod.id !== product_id) return prod; prod.pricePerUnit = pricePerUnit; return prod; }); return state; }, updateDimensions( state, action: PayloadAction<{ product_id: Id; dimensions: dimensions_t }> ) { if (!state) return initialState; const { product_id, dimensions } = action.payload; console.log("Changing dimensions: %o", action.payload); return { ...state, products: state.products.map((prod) => { if (prod.id !== product_id) return prod; return { ...prod, dimensions, }; }), }; }, }, }); export const selectProducts = (state: RootState) => { return state.products; }; export const selectPlywoodCalc = (state: RootState) => { return state.calculations.plywood; }; export const selectCarpetCalc = (state: RootState) => { return state.calculations.carpet; }; export const selectProductIds = createSelector([selectProducts], (products) => { return products.map((p) => p.id); }); export const selectProductAttributes = createSelector( [selectProducts], (products) => { return Object.fromEntries( products.map((p) => { return [p.id, p.attributes ? attributesAsList(p.attributes) : []]; }) ); } ); export const actions = { ...productsState.actions, }; export const { setCarpetCalculation, setPlywoodCalculation, createProduct, deleteProduct, changeKey, updateAttribute, addAttribute, deleteAttribute, updatePrice, updateDimensions, } = productsState.actions; export default productsState.reducer;