PliWould/features/product/productSlice.ts

275 lines
6.8 KiB
TypeScript

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<PlywoodCalculationState>
) {
if (!state) {
return initialState;
}
const newCalc = action.payload;
state.calculations.plywood = newCalc;
return state;
},
setCarpetCalculation(state, action: PayloadAction<CarpetCalculationState>) {
if (!state) {
return initialState;
}
const newCalc = action.payload;
state.calculations.carpet = newCalc;
return state;
},
createProduct(state, action: PayloadAction<Product>) {
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<Id>) {
if (!state) return initialState;
return {
...state,
products: [
...state.products.filter((prod) => {
return prod.id?.valueOf() !== action.payload.valueOf();
}),
],
};
},
updateAttribute(state, action: PayloadAction<UpdateAttribute>) {
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<UpdateAttributeKey>) {
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<Id>) {
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;