did some more cleanup of the interface and UI interactions.

This commit is contained in:
Jordan 2024-07-02 08:34:23 -07:00
parent 466e005e4e
commit bf3923b4b9
10 changed files with 112 additions and 38 deletions

View File

@ -2,7 +2,7 @@ import { Product } from "@/lib/product";
export const products = [ export const products = [
// Sheet goods // Sheet goods
new Product(25, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/4\"" }), new Product(15, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/4\"" }),
new Product(20, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/2\"" }), new Product(20, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/2\"" }),
new Product(25, {l: 4, w : 8, u: "ft"}, { name: "Plywood 3/4\"" }), new Product(25, {l: 4, w : 8, u: "ft"}, { name: "Plywood 3/4\"" }),
new Product(5, {l: 4, w : 8, u: "ft"}, { name: "Thin Panel Board" }), new Product(5, {l: 4, w : 8, u: "ft"}, { name: "Thin Panel Board" }),

View File

@ -2,10 +2,11 @@
"expo": { "expo": {
"name": "PliWould", "name": "PliWould",
"slug": "PliWould", "slug": "PliWould",
"scheme": "tech.damngood.PliWould",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/images/icon.png", "icon": "./assets/images/icon.png",
"scheme": "myapp", "entrypoint": "./app/app.tsx",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"splash": { "splash": {
"image": "./assets/images/splash.png", "image": "./assets/images/splash.png",

View File

@ -10,7 +10,8 @@ import { setupStore } from '../store';
export default function TabLayout() { export default function TabLayout() {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const store = setupStore({ const store = setupStore({
products: fixtures.map(p => p.asObject) products: fixtures.map(p => p.asObject),
units: "ft",
}); });
return ( return (
<Provider store={store}> <Provider store={store}>
@ -22,7 +23,7 @@ export default function TabLayout() {
<Tabs.Screen <Tabs.Screen
name="index" name="index"
options={{ options={{
title: 'Home Screen', title: 'Measure',
tabBarIcon: ({ color, focused }) => ( tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'scale' : 'scale-outline'} color={color} /> <TabBarIcon name={focused ? 'scale' : 'scale-outline'} color={color} />
), ),
@ -31,7 +32,7 @@ export default function TabLayout() {
<Tabs.Screen <Tabs.Screen
name="product-editor" name="product-editor"
options={{ options={{
title: 'Products', title: 'Edit Products',
tabBarIcon: ({ color, focused }) => ( tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? 'list' : 'list-outline'} color={color} /> <TabBarIcon name={focused ? 'list' : 'list-outline'} color={color} />
), ),

View File

@ -3,25 +3,29 @@ import { configureStore } from '@reduxjs/toolkit';
import { rememberReducer, rememberEnhancer } from 'redux-remember'; import { rememberReducer, rememberEnhancer } from 'redux-remember';
import reducers from "@/features/product/productSlice" import reducers from "@/features/product/productSlice"
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { Product, ProductData, } from "@/lib/product"; import { ProductData, } from "@/lib/product";
import {Length} from "convert"
const rememberedKeys = ['products']; const rememberedKeys = ['products'];
const rootReducer = reducers; const rootReducer = reducers;
const isBrowser = (typeof window !== "undefined");
export function setupStore(preloadedState = { export function setupStore(preloadedState = {
products: [] as ProductData[], products: [] as ProductData[],
units: "ft" as Length,
}) { }) {
return configureStore({ return configureStore({
reducer: rememberReducer(reducers), reducer: rememberReducer(reducers),
preloadedState, preloadedState,
enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat( enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(
rememberEnhancer( rememberEnhancer(
AsyncStorage, isBrowser ? window.localStorage : AsyncStorage,
rememberedKeys, rememberedKeys,
{ {
persistWholeStore: true, persistWholeStore: false,
} },
) )
), ),
}); });

View File

@ -1,18 +1,21 @@
import { MeasurementInput } from "./MeasurementInput"; import { MeasurementInput } from "./MeasurementInput";
import { area_t, dimensions_t } from "@/lib/product"; import { area_t, dimensions_t } from "@/lib/product";
import { Length } from "convert";
import { useState } from "react"; import { useState } from "react";
import { StyleSheet, View } from "react-native"; import { StyleSheet, Text, View } from "react-native";
export type AreaInputProps = { export type AreaInputProps = {
onMeasurementSet?: (area : dimensions_t) => any, onMeasurementSet?: (area : dimensions_t) => any,
defaultValue?: area_t, defaultValue?: area_t,
lengthLabel?: string, lengthLabel?: string,
widthLabel?: string, widthLabel?: string,
units?: Length,
} }
export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue} : AreaInputProps) { export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue, units} : AreaInputProps) {
defaultValue = defaultValue || {l: 0, w: 0, u: "ft"} defaultValue = defaultValue || {l: 0, w: 0, u: "ft"}
units = units || "ft"
const [area, setArea] = useState(defaultValue) const [area, setArea] = useState(defaultValue)
@ -44,11 +47,14 @@ export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultVal
defaultValue={{l: area.l, u: area.u}} defaultValue={{l: area.l, u: area.u}}
onValueSet={doOnLengthSet} onValueSet={doOnLengthSet}
label={lengthLabel} label={lengthLabel}
units={units}
/> />
<Text style={{fontSize: 30,}} > x </Text>
<MeasurementInput <MeasurementInput
defaultValue={{l: area.w, u: area.u}} defaultValue={{l: area.w, u: area.u}}
onValueSet={doOnWidthSet} onValueSet={doOnWidthSet}
label={widthLabel} label={widthLabel}
units={units}
/> />
</View> </View>
) )
@ -56,6 +62,7 @@ export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultVal
const styles = StyleSheet.create({ const styles = StyleSheet.create({
areaInputWrapper: { areaInputWrapper: {
flexDirection: "row" flexDirection: "row",
verticalAlign: "middle",
} }
}) })

View File

@ -9,13 +9,16 @@ export type MeasurementInputProps = {
onValueSet?: (d: dimensions_t) => any, onValueSet?: (d: dimensions_t) => any,
defaultValue: length_t; defaultValue: length_t;
label?: string, label?: string,
units?: Length,
} }
export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementInputProps) { export function MeasurementInput({onValueSet, defaultValue, label, units}: MeasurementInputProps) {
const [mValue, setMValue] = useState(defaultValue) const [mValue, setMValue] = useState(defaultValue)
const defValue = Number.isNaN(defaultValue.l) ? 0 : defaultValue.l const defValue = Number.isNaN(defaultValue.l) ? 0 : defaultValue.l
units = units || "ft";
function doOnValueSet(value : string) { function doOnValueSet(value : string) {
setMValue(mValue); setMValue(mValue);
const iVal = parseFloat(value) || parseInt(value); const iVal = parseFloat(value) || parseInt(value);
@ -37,7 +40,7 @@ export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementI
style={styles.lengthInput} style={styles.lengthInput}
aria-label={label || "Enter measurement"} aria-label={label || "Enter measurement"}
/> />
<Text style={styles.unitHints}>{mValue.u}</Text> <Text style={styles.unitHints}>{units}</Text>
</View> </View>
) )
} }
@ -45,10 +48,13 @@ export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementI
const styles = StyleSheet.create({ const styles = StyleSheet.create({
inputWrapper: { inputWrapper: {
alignItems: "flex-start", alignItems: "flex-start",
flexDirection: "row" flexDirection: "row",
verticalAlign: "middle"
}, },
unitHints: { unitHints: {
padding: 10, padding: 10,
fontSize: 20,
verticalAlign: "middle",
}, },
lengthInput: { lengthInput: {
borderWidth: 1, borderWidth: 1,

View File

@ -1,27 +1,36 @@
import { StyleSheet, Text, TextInput, View } from "react-native"; import { StyleSheet, Text, TextInput, View } from "react-native";
import Slider from '@react-native-community/slider'; import Slider from '@react-native-community/slider';
import { useState } from "react"; import { useEffect, useState } from "react";
type PercentDamageProps = { type PercentDamageProps = {
onSetPercentage: (percent: number) => any; onSetPercentage: (percent: number) => any;
} }
function getDamangeColor(damage : number) {
if (damage === 0) return "black";
if (damage <= 20) return "blue";
if (damage <= 50) return "orange";
return "red";
}
export default function PercentDamage({ onSetPercentage }: PercentDamageProps) { export default function PercentDamage({ onSetPercentage }: PercentDamageProps) {
const [damage, setDamage] = useState(0); const [damage, setDamage] = useState(0);
const [damageColor, setDamageColor] = useState("black");
function doOnChangeText(val: number) { function doOnChangeText(val: number) {
setDamage(val); setDamage(val || 0);
onSetPercentage(val / 100); onSetPercentage((val / 100) || 0);
setDamageColor(getDamangeColor(val || 0));
} }
return ( return (
<View style={styles.wrapper}> <View style={styles.wrapper}>
<Slider <Slider
value={damage}
minimumValue={0} minimumValue={0}
maximumValue={100} maximumValue={100}
step={5} step={5}
onValueChange={doOnChangeText} onValueChange={doOnChangeText}
/> />
<Text style={styles.label}> {damage}% Damage</Text> <Text style={{ ...styles.label, color: damageColor }}> {damage}% Damage</Text>
</View> </View>
) )
} }
@ -40,5 +49,9 @@ const styles = StyleSheet.create({
}, },
label: { label: {
margin: 5, margin: 5,
alignSelf: "center",
fontSize: 20,
fontWeight: "bold",
fontStyle: "italic",
} }
}) })

View File

@ -83,12 +83,14 @@ export default function ProductCalculatorSelector() {
onMeasurementSet={onMeasurementSet} onMeasurementSet={onMeasurementSet}
widthLabel='enter width' widthLabel='enter width'
lengthLabel='enter length' lengthLabel='enter length'
units={measurement.u}
/> />
: :
<MeasurementInput <MeasurementInput
defaultValue={activeProduct.dimensions} defaultValue={activeProduct.dimensions}
onValueSet={onMeasurementSet} onValueSet={onMeasurementSet}
label="enter length" label="enter length"
units={measurement.u}
/> />
) : ( ) : (
@ -101,7 +103,7 @@ export default function ProductCalculatorSelector() {
</View> </View>
</View> </View>
{activeProduct && {activeProduct &&
(<View > (<View style={styles.damageWrapper}>
<PercentDamage <PercentDamage
onSetPercentage={onSetPercentDamage} onSetPercentage={onSetPercentDamage}
/> />
@ -129,6 +131,7 @@ export const styles = StyleSheet.create({
inputWrapper: { inputWrapper: {
flexDirection: "row", flexDirection: "row",
alignItems: "flex-start", alignItems: "flex-start",
verticalAlign: "middle",
}, },
unitSelector: { unitSelector: {
}, },
@ -195,4 +198,9 @@ export const styles = StyleSheet.create({
padding: 4, padding: 4,
}, },
damageWrapper: {
paddingVertical: 10,
paddingHorizontal: 10,
},
}); });

View File

@ -1,16 +1,17 @@
import { Length } from "convert"; import { Length } from "convert";
import { useState } from "react"; import { useState } from "react";
import { Button, StyleSheet, View } from "react-native"; import { Button, StyleSheet, Text, TouchableHighlight, View } from "react-native";
export type UnitChooserProps = { export type UnitChooserProps = {
choices: Length[], choices: Length[],
onChoicePressed: (l: Length) => any, onChoicePressed: (l: Length) => any,
activeColor?: string, activeColor?: string,
defaultColor?: string, defaultColor?: string,
defaultValue? : Length,
} }
export default function UnitChooser({ choices, onChoicePressed, activeColor, defaultColor }: UnitChooserProps) { export default function UnitChooser({ choices, onChoicePressed, activeColor, defaultColor, defaultValue }: UnitChooserProps) {
const [value, setValue] = useState(choices[0] as Length); const [value, setValue] = useState(defaultValue || choices[0] as Length);
activeColor = activeColor || "lightblue"; activeColor = activeColor || "lightblue";
defaultColor = defaultColor || "lightgrey"; defaultColor = defaultColor || "lightgrey";
@ -24,11 +25,13 @@ export default function UnitChooser({ choices, onChoicePressed, activeColor, def
<View style={styles.unitChooser}> <View style={styles.unitChooser}>
{choices.map((ci) => { {choices.map((ci) => {
return ( return (
<Button <TouchableHighlight
title={ci}
onPress={() => doChoiceClicked(ci)} onPress={() => doChoiceClicked(ci)}
color={value === ci ? activeColor : defaultColor} style={value === ci ? styles.active : styles.default }
/> key={ci}
>
<Text style={value === ci ? styles.textActive : styles.textDefault}>{ci}</Text>
</TouchableHighlight>
) )
}) })
} }
@ -38,12 +41,34 @@ export default function UnitChooser({ choices, onChoicePressed, activeColor, def
const styles = StyleSheet.create({ const styles = StyleSheet.create({
unitChooser: { unitChooser: {
flexDirection: "row",
verticalAlign: "middle",
}, },
active: { active: {
backgroundColor: "skyblue",
padding: 5,
borderRadius: 5,
}, },
default: { default: {
backgroundColor: "lightgray",
} padding: 5,
borderRadius: 5,
verticalAlign: "middle",
},
textActive: {
marginTop: 2,
marginBottom: 2,
marginLeft: 10,
marginRight: 10,
fontSize: 40,
},
textDefault: {
marginTop: 2,
marginBottom: 2,
marginLeft: 10,
marginRight: 10,
fontSize: 40,
},
unitButton: {
},
}) })

View File

@ -1,12 +1,13 @@
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { area_t, dimensions_t, Id, length_t, Product, ProductData } from '@/lib/product'; import { dimensions_t, Id, Product, ProductData } from '@/lib/product';
import uuid from "react-native-uuid"; import uuid from "react-native-uuid";
import { RootState } from '@/app/store'; import { RootState } from '@/app/store';
import { classToPlain, plainToClass } from 'class-transformer'; import { Length } from 'convert';
const initialState = { const initialState = {
products: [] as ProductData[], products: [] as ProductData[],
} units: "ft",
};
export type UpdateAttribute = { export type UpdateAttribute = {
product_id: Id, product_id: Id,
@ -30,6 +31,9 @@ const productsState = createSlice({
name: 'products-slice', name: 'products-slice',
initialState, initialState,
reducers: { reducers: {
setUnits(state, action : PayloadAction<Length>) {
state.units = action.payload;
},
createProduct(state, action: PayloadAction<ProductData>) { createProduct(state, action: PayloadAction<ProductData>) {
if (!state) { if (!state) {
return initialState return initialState
@ -150,6 +154,10 @@ export const selectProductsDatas = (state: RootState) => {
return state.products; return state.products;
} }
export const selectUnits = (state : RootState) => {
return (state.units || "ft") as Length;
}
export const selectProducts = createSelector([selectProductsDatas], productsData => { export const selectProducts = createSelector([selectProductsDatas], productsData => {
return productsData.map(d => Product.fromObject(d)); return productsData.map(d => Product.fromObject(d));
}) })
@ -172,6 +180,7 @@ export const actions = {
}; };
export const { export const {
setUnits,
createProduct, createProduct,
deleteProduct, deleteProduct,
changeKey, changeKey,