good enough for government (or habitat) work
This commit is contained in:
parent
379f43dcd9
commit
ecdc9db085
@ -2,11 +2,20 @@ import { Product } from "@/lib/product";
|
|||||||
|
|
||||||
export const products = [
|
export const products = [
|
||||||
// Sheet goods
|
// Sheet goods
|
||||||
new Product(25, {l: 4, w : 8, u: "feet"}, { name: "Plywood" }),
|
new Product(25, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/4\"" }),
|
||||||
new Product(35, {l: 4, w : 8, u: "feet"}, { name: "MDF" }),
|
new Product(20, {l: 4, w : 8, u: "ft"}, { name: "Plywood 1/2\"" }),
|
||||||
new Product(40, {l: 4, w : 8, u: "feet"}, { name: "OSB" }),
|
new Product(25, {l: 4, w : 8, u: "ft"}, { name: "Plywood 3/4\"" }),
|
||||||
new Product(45, {l: 4, w : 8, u: "feet"}, { name: "Sheetrock" }),
|
new Product(5, {l: 4, w : 8, u: "ft"}, { name: "Thin Panel Board" }),
|
||||||
// Beams and trim
|
new Product(10, {l: 4, w : 8, u: "ft"}, { name: "Sheetrock" }),
|
||||||
new Product(1, {l: 0.50, u : "feet"}, { name: "trim 3 inches" }),
|
new Product(15, {l: 4, w : 8, u: "ft"}, { name: "OSB / Particle" }),
|
||||||
new Product(1, {l: 0.75, u : "feet"}, { name: "trim 3 inches" }),
|
new Product(20, {l: 4, w : 8, u: "ft"}, { name: "MDF" }),
|
||||||
|
new Product(15, {l: 4, w : 8, u: "ft"}, { name: "Pegboard" }),
|
||||||
|
new Product(5, {l: 3, w : 5, u: "ft"}, { name: "Cement" }),
|
||||||
|
// trim
|
||||||
|
new Product(1, {l: 0.50, u : "ft"}, { name: "trim <= 3 inches" }),
|
||||||
|
new Product(1, {l: 0.75, u : "ft"}, { name: "trim > 3 inches" }),
|
||||||
|
// siding
|
||||||
|
new Product(1, {l: 1, u: "ft"}, {name: "house siding"}),
|
||||||
|
new Product(1, {l: 1, u: "ft"}, {name: "metal / shelf bars"}),
|
||||||
|
new Product(0.5, {l: 1, u: "ft"}, {name: "gutter spouts"}),
|
||||||
];
|
];
|
@ -22,9 +22,9 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: 'Conversion',
|
title: 'Home Screen',
|
||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<TabBarIcon name={focused ? 'recording' : 'recording-outline'} color={color} />
|
<TabBarIcon name={focused ? 'scale' : 'scale-outline'} color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -33,7 +33,7 @@ export default function TabLayout() {
|
|||||||
options={{
|
options={{
|
||||||
title: 'Products',
|
title: 'Products',
|
||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<TabBarIcon name={focused ? 'recording' : 'recording-outline'} color={color} />
|
<TabBarIcon name={focused ? 'list' : 'list-outline'} color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import ProductCalculatorSelector from '@/components/ProductCalculatorSelector';
|
import ProductCalculatorSelector from '@/components/ProductCalculatorSelector';
|
||||||
import { SafeAreaView, View } from 'react-native';
|
import { SafeAreaView, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
export default function Convert () {
|
||||||
const fallbackImage = require("@/assets/images/board-stock-lightened-blurred.png");
|
|
||||||
|
|
||||||
export const HomeScreen = () => {
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<View>
|
||||||
<ProductCalculatorSelector />
|
<ProductCalculatorSelector />
|
||||||
</SafeAreaView>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 { useState } from "react";
|
import { useState } from "react";
|
||||||
import { View } from "react-native";
|
import { StyleSheet, View } from "react-native";
|
||||||
|
|
||||||
export type AreaInputProps = {
|
export type AreaInputProps = {
|
||||||
onMeasurementSet?: (area : dimensions_t) => any,
|
onMeasurementSet?: (area : dimensions_t) => any,
|
||||||
@ -12,7 +12,7 @@ export type AreaInputProps = {
|
|||||||
|
|
||||||
export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue} : AreaInputProps) {
|
export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultValue} : AreaInputProps) {
|
||||||
|
|
||||||
defaultValue = defaultValue || {l: 0, w: 0, u: "foot"}
|
defaultValue = defaultValue || {l: 0, w: 0, u: "ft"}
|
||||||
|
|
||||||
const [area, setArea] = useState(defaultValue)
|
const [area, setArea] = useState(defaultValue)
|
||||||
|
|
||||||
@ -39,19 +39,23 @@ export function AreaInput({onMeasurementSet, lengthLabel, widthLabel, defaultVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={styles.areaInputWrapper}>
|
||||||
<MeasurementInput
|
<MeasurementInput
|
||||||
units={area.u}
|
defaultValue={{l: area.l, u: area.u}}
|
||||||
defaultValue={area.l}
|
|
||||||
onValueSet={doOnLengthSet}
|
onValueSet={doOnLengthSet}
|
||||||
label={lengthLabel}
|
label={lengthLabel}
|
||||||
/>
|
/>
|
||||||
<MeasurementInput
|
<MeasurementInput
|
||||||
units={area.u}
|
defaultValue={{l: area.w, u: area.u}}
|
||||||
defaultValue={area.w}
|
|
||||||
onValueSet={doOnWidthSet}
|
onValueSet={doOnWidthSet}
|
||||||
label={widthLabel}
|
label={widthLabel}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
areaInputWrapper: {
|
||||||
|
flexDirection: "row"
|
||||||
|
}
|
||||||
|
})
|
@ -1,5 +1,6 @@
|
|||||||
import { dimensions_t, length_t } from "@/lib/product";
|
import { dimensions_t, length_t } from "@/lib/product";
|
||||||
import { Length } from "convert";
|
import { Length } from "convert";
|
||||||
|
import { useState } from "react";
|
||||||
import { StyleSheet, Text, TextInput, View } from "react-native";
|
import { StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
|
|
||||||
export type t_length_unit = "foot" | "inch"
|
export type t_length_unit = "foot" | "inch"
|
||||||
@ -11,8 +12,12 @@ export type MeasurementInputProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementInputProps) {
|
export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementInputProps) {
|
||||||
1
|
|
||||||
|
const [mValue, setMValue] = useState(defaultValue)
|
||||||
|
const defValue = Number.isNaN(defaultValue.l) ? 0 : defaultValue.l
|
||||||
|
|
||||||
function doOnValueSet(value : string) {
|
function doOnValueSet(value : string) {
|
||||||
|
setMValue(mValue);
|
||||||
const iVal = parseFloat(value) || parseInt(value);
|
const iVal = parseFloat(value) || parseInt(value);
|
||||||
onValueSet && onValueSet({
|
onValueSet && onValueSet({
|
||||||
...defaultValue,
|
...defaultValue,
|
||||||
@ -20,10 +25,10 @@ export function MeasurementInput({onValueSet, defaultValue, label}: MeasurementI
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const sDefValue = new String(defaultValue.l).valueOf()
|
const sDefValue = new String(defValue).valueOf()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={styles.inputWrapper}>
|
||||||
<TextInput
|
<TextInput
|
||||||
clearTextOnFocus={true}
|
clearTextOnFocus={true}
|
||||||
defaultValue={sDefValue}
|
defaultValue={sDefValue}
|
||||||
@ -32,14 +37,17 @@ 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}>{defaultValue.u}</Text>
|
<Text style={styles.unitHints}>{mValue.u}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
inputWrapper: {
|
||||||
|
alignItems: "flex-start",
|
||||||
|
flexDirection: "row"
|
||||||
|
},
|
||||||
unitHints: {
|
unitHints: {
|
||||||
fontSize: 30,
|
|
||||||
padding: 10,
|
padding: 10,
|
||||||
},
|
},
|
||||||
lengthInput: {
|
lengthInput: {
|
||||||
@ -48,7 +56,7 @@ const styles = StyleSheet.create({
|
|||||||
borderColor: "grey",
|
borderColor: "grey",
|
||||||
padding: 4,
|
padding: 4,
|
||||||
margin: 4,
|
margin: 4,
|
||||||
fontSize: 30,
|
fontSize: 25,
|
||||||
width: 200,
|
width: 100,
|
||||||
},
|
},
|
||||||
})
|
})
|
44
components/PercentDamange.tsx
Normal file
44
components/PercentDamange.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { StyleSheet, Text, TextInput, View } from "react-native";
|
||||||
|
import Slider from '@react-native-community/slider';
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type PercentDamageProps = {
|
||||||
|
onSetPercentage: (percent: number) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PercentDamage ({onSetPercentage} : PercentDamageProps) {
|
||||||
|
const [damage, setDamage] = useState(0);
|
||||||
|
function doOnChangeText (val : number) {
|
||||||
|
setDamage(val);
|
||||||
|
onSetPercentage(val / 100);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.wrapper}>
|
||||||
|
<Slider
|
||||||
|
value={damage}
|
||||||
|
minimumValue={0}
|
||||||
|
maximumValue={100}
|
||||||
|
step={5}
|
||||||
|
onValueChange={doOnChangeText}
|
||||||
|
/>
|
||||||
|
<Text style={styles.label}> {damage}% Damage</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
wrapper: {
|
||||||
|
padding: 5,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
margin: 5,
|
||||||
|
padding: 5,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "lightgrey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
margin: 5,
|
||||||
|
}
|
||||||
|
})
|
@ -32,7 +32,7 @@ export const styles = StyleSheet.create({
|
|||||||
bigPrice: {
|
bigPrice: {
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
fontSize: 40,
|
fontSize: 40,
|
||||||
marginTop: 100,
|
marginTop: 50,
|
||||||
marginBottom: 100,
|
marginBottom: 50,
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -1,48 +1,75 @@
|
|||||||
import { useAppSelector } from '@/app/store';
|
|
||||||
import { selectProducts } from '@/features/product/productSlice';
|
|
||||||
import { Product, dimensions_t } from '@/lib/product';
|
import { Product, dimensions_t } from '@/lib/product';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import { TouchableHighlight } from 'react-native-gesture-handler';
|
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import PriceDisplay from './Price';
|
import PriceDisplay from './Price';
|
||||||
import { AreaInput } from './AreaInput';
|
import { AreaInput } from './AreaInput';
|
||||||
import { MeasurementInput } from './MeasurementInput';
|
import { MeasurementInput } from './MeasurementInput';
|
||||||
import ProductList from './ProductList';
|
import ProductList from './ProductList';
|
||||||
import UnitChooser from './UnitChooser';
|
import UnitChooser from './UnitChooser';
|
||||||
import { Length } from 'convert';
|
import convert, { Length } from 'convert';
|
||||||
|
import PercentDamage from './PercentDamange';
|
||||||
|
|
||||||
|
|
||||||
export default function ProductCalculatorSelector() {
|
export default function ProductCalculatorSelector() {
|
||||||
|
|
||||||
const products = useAppSelector(selectProducts);
|
|
||||||
const [activeProduct, setActiveProduct] = useState(null as Product | null);
|
const [activeProduct, setActiveProduct] = useState(null as Product | null);
|
||||||
const [price, setPrice] = useState(0);
|
const [price, setPrice] = useState(0);
|
||||||
const [measurement, setMeasurement] = useState({l: 0, w: 0, u: "ft"} as dimensions_t);
|
const [measurement, setMeasurement] = useState({ l: 0, w: 0, u: "ft" } as dimensions_t);
|
||||||
|
const [percentDamage, setPercentDamange] = useState(0.0);
|
||||||
|
|
||||||
useEffect(function () {
|
useEffect(function () {
|
||||||
const iv = setInterval(function () {
|
const iv = setInterval(function () {
|
||||||
if (!(activeProduct && measurement)) return;
|
if (!(activeProduct && measurement)) return;
|
||||||
setPrice(
|
setPrice(
|
||||||
activeProduct.priceFor(measurement)
|
activeProduct.priceFor(measurement, percentDamage)
|
||||||
)
|
)
|
||||||
}, 50);
|
}, 50);
|
||||||
return function () {
|
return function () {
|
||||||
clearInterval(iv);
|
clearInterval(iv);
|
||||||
};
|
};
|
||||||
}, [activeProduct, measurement]);
|
}, [activeProduct, measurement, percentDamage]);
|
||||||
|
|
||||||
function onMeasurementSet(dimensions: dimensions_t) {
|
function onMeasurementSet(dimensions: dimensions_t) {
|
||||||
setMeasurement(dimensions);
|
setMeasurement(dimensions);
|
||||||
|
activeProduct && setPrice(
|
||||||
|
activeProduct.priceFor(measurement, percentDamage)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUnitChosen(unit : Length) {
|
function onUnitChosen(unit: Length) {
|
||||||
setMeasurement({
|
setMeasurement({
|
||||||
...measurement,
|
...measurement,
|
||||||
u: unit,
|
u: unit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSetPercentDamage(pct: number) {
|
||||||
|
setPercentDamange(pct);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onProductSelected(product: Product) {
|
||||||
|
setActiveProduct(product);
|
||||||
|
setMeasurement(
|
||||||
|
{
|
||||||
|
l: convert(
|
||||||
|
product.dimensions.l,
|
||||||
|
product.dimensions.u,
|
||||||
|
).to(measurement.u),
|
||||||
|
u: measurement.u,
|
||||||
|
...(
|
||||||
|
("w" in measurement && "w" in product.dimensions) ? {
|
||||||
|
w: convert(
|
||||||
|
product.dimensions.w,
|
||||||
|
product.dimensions.u,
|
||||||
|
).to(measurement.u),
|
||||||
|
u: measurement.u,
|
||||||
|
} : {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.wrapper}>
|
<SafeAreaView style={styles.wrapper}>
|
||||||
<PriceDisplay price={price} />
|
<PriceDisplay price={price} />
|
||||||
@ -73,7 +100,14 @@ export default function ProductCalculatorSelector() {
|
|||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<ProductList onProductSelected={setActiveProduct} />
|
{activeProduct &&
|
||||||
|
(<View >
|
||||||
|
<PercentDamage
|
||||||
|
onSetPercentage={onSetPercentDamage}
|
||||||
|
/>
|
||||||
|
</View>)
|
||||||
|
}
|
||||||
|
<ProductList onProductSelected={onProductSelected} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -81,6 +115,7 @@ export default function ProductCalculatorSelector() {
|
|||||||
|
|
||||||
export const styles = StyleSheet.create({
|
export const styles = StyleSheet.create({
|
||||||
wrapper: {
|
wrapper: {
|
||||||
|
overflow: "scroll"
|
||||||
},
|
},
|
||||||
bigPriceWrapper: {
|
bigPriceWrapper: {
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
|
@ -39,8 +39,8 @@ export const ProductEditor = ({}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView style={{overflow: "scroll"}}>
|
||||||
<h1 style={styles.h1}>Edit Products</h1>
|
<Text>Edit Products</Text>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={products}
|
data={products}
|
||||||
keyExtractor={(p, i) => `product-${p.id}`}
|
keyExtractor={(p, i) => `product-${p.id}`}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { Id, Product, dimensions_t } from "@/lib/product"
|
import { Id, Product, dimensions_t } from "@/lib/product"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Button, FlatList, StyleSheet, Text, Touchable, TouchableHighlight, View } from "react-native"
|
import { Button, FlatList, StyleSheet, Text, TextInput, Touchable, TouchableHighlight, View } from "react-native"
|
||||||
import { ProductAttributeEditor } from "./ProductAttributeEditor";
|
import { ProductAttributeEditor } from "./ProductAttributeEditor";
|
||||||
import { TextInput } from "react-native-gesture-handler";
|
import { Dropdown } from 'react-native-element-dropdown';
|
||||||
import { useAppSelector } from "@/app/store";
|
|
||||||
import rfdc from "rfdc";
|
|
||||||
import SelectDropdown from "react-native-select-dropdown";
|
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { Length } from "convert";
|
||||||
|
|
||||||
export type ProductAddedFunc = () => any;
|
export type ProductAddedFunc = () => any;
|
||||||
export type ProductDeletedFunc = (product_id: Id) => any;
|
export type ProductDeletedFunc = (product_id: Id) => any;
|
||||||
@ -50,9 +48,9 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
props.onPriceUpdated && props.onPriceUpdated(product.id, parseFloat(pricePerUnit) || parseInt(pricePerUnit));
|
props.onPriceUpdated && props.onPriceUpdated(product.id, parseFloat(pricePerUnit) || parseInt(pricePerUnit));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUnitsChanged(newUnits: "foot" | "inch") {
|
function onUnitsChanged(newUnits: Length) {
|
||||||
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
||||||
...((product.area || product.length) as dimensions_t),
|
...(product.dimensions as dimensions_t),
|
||||||
u: newUnits,
|
u: newUnits,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -60,16 +58,16 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
function onChangeLength(len: string) {
|
function onChangeLength(len: string) {
|
||||||
const l = parseFloat(len) || parseInt(len);
|
const l = parseFloat(len) || parseInt(len);
|
||||||
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
||||||
...((product.area || product.length) as dimensions_t),
|
...(product.dimensions as dimensions_t),
|
||||||
l,
|
l,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeWidth(width: string) {
|
function onChangeWidth(width: string) {
|
||||||
const w = parseFloat(width) || parseInt(width);
|
const w = width.length == 0 ? null : parseFloat(width) || parseInt(width);
|
||||||
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
props.onDimensionsUpdated && props.onDimensionsUpdated(product.id, {
|
||||||
...((product.area || product.length) as dimensions_t),
|
...(product.dimensions as dimensions_t),
|
||||||
w,
|
...(w ? {w} : {}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,31 +75,36 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
props.onProductDeleted && props.onProductDeleted(product.id);
|
props.onProductDeleted && props.onProductDeleted(product.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const length = new String(product.area?.l || product.length?.l || "0") as string;
|
const length = new String(product.dimensions.l || product.dimensions.l || "0") as string;
|
||||||
const width = new String(product.area?.w || "") as string;
|
const width = new String(product.dimensions.w || "") as string;
|
||||||
const dimension = product.area?.u || product.length?.u || "foot";
|
const dimension = product.dimensions.u || product.dimensions.u || "foot";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<TouchableHighlight
|
<View style={styles.productListHeader}>
|
||||||
onPress={() => setShowAttributes(!showAttributes)}
|
<TouchableHighlight
|
||||||
aria-label="Product Item"
|
onPress={() => setShowAttributes(!showAttributes)}
|
||||||
style={styles.productItemName}
|
aria-label="Product Item"
|
||||||
>
|
style={styles.productItemName}
|
||||||
<Text style={styles.productNameText}>{product.attributes.name || `Product ${product.id}`}</Text>
|
|
||||||
</TouchableHighlight>
|
|
||||||
<TouchableHighlight
|
|
||||||
onPress={() => onDeleteProduct()}
|
|
||||||
aria-label="delete product"
|
|
||||||
style={styles.deleteProductHighlight}
|
|
||||||
>
|
>
|
||||||
<Ionicons style={styles.deleteProductButton} name="trash-outline" />
|
<Text style={styles.productNameText}>{product.attributes.name || `Product ${product.id}`}</Text>
|
||||||
</TouchableHighlight>
|
</TouchableHighlight>
|
||||||
|
<TouchableHighlight
|
||||||
|
onPress={() => onDeleteProduct()}
|
||||||
|
aria-label="delete product"
|
||||||
|
style={styles.deleteProductHighlight}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
style={styles.deleteProductButton}
|
||||||
|
name="trash-outline"
|
||||||
|
/>
|
||||||
|
</TouchableHighlight>
|
||||||
|
</View>
|
||||||
{showAttributes &&
|
{showAttributes &&
|
||||||
(
|
(
|
||||||
<View>
|
<View style={styles.detailsWrapper}>
|
||||||
<View style={styles.priceSpecWrapper}>
|
<View style={styles.priceSpecWrapper}>
|
||||||
<Text style={styles.priceLabel}></Text>
|
<Text style={styles.priceLabel}>$</Text>
|
||||||
<TextInput inputMode="decimal"
|
<TextInput inputMode="decimal"
|
||||||
defaultValue={new String(product.pricePerUnit) as string}
|
defaultValue={new String(product.pricePerUnit) as string}
|
||||||
aria-label="price per unit"
|
aria-label="price per unit"
|
||||||
@ -109,14 +112,18 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
style={styles.priceInput}
|
style={styles.priceInput}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.per}>per</Text>
|
<Text style={styles.per}>per</Text>
|
||||||
<Text style={styles.unitsLabel}>Units: </Text>
|
<Dropdown
|
||||||
<select
|
data={[
|
||||||
onChange={(e) => onUnitsChanged(e.target.value as "foot" | "inch")}
|
{label: "feet", value: "ft"},
|
||||||
|
{label: "inches", value: "in"},
|
||||||
|
]}
|
||||||
style={styles.unitsSelect}
|
style={styles.unitsSelect}
|
||||||
aria-label="units">
|
mode="modal"
|
||||||
<option value="foot" selected={dimension === "foot"}>feet</option>
|
labelField="label"
|
||||||
<option value="inch" selected={dimension === "inch"}>inches</option>
|
valueField="value"
|
||||||
</select>
|
value={product.dimensions.u || "ft"}
|
||||||
|
onChange={(item) => onUnitsChanged(item.value as Length)}
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
defaultValue={length}
|
defaultValue={length}
|
||||||
@ -124,7 +131,7 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
style={styles.lengthInput}
|
style={styles.lengthInput}
|
||||||
aria-label="length"
|
aria-label="length"
|
||||||
/>
|
/>
|
||||||
<Text>x</Text>
|
<Text style={{flex: 1,}}>x</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
defaultValue={width}
|
defaultValue={width}
|
||||||
@ -158,33 +165,50 @@ export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
deleteProductHighlight: {
|
deleteProductHighlight: {
|
||||||
|
|
||||||
|
padding: 5,
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
deleteProductButton: {
|
deleteProductButton: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
detailsWrapper: {
|
||||||
|
|
||||||
},
|
},
|
||||||
priceSpecWrapper: {
|
priceSpecWrapper: {
|
||||||
|
flexDirection: "row",
|
||||||
},
|
},
|
||||||
priceLabel: {
|
priceLabel: {
|
||||||
|
|
||||||
},
|
},
|
||||||
priceInput: {
|
priceInput: {
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "lightgrey",
|
||||||
|
borderStyle: "solid",
|
||||||
},
|
},
|
||||||
per: {
|
per: {
|
||||||
|
padding: 5,
|
||||||
},
|
},
|
||||||
unitsLabel: {
|
unitsLabel: {
|
||||||
|
|
||||||
},
|
},
|
||||||
unitsSelect: {
|
unitsSelect: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 5,
|
||||||
},
|
},
|
||||||
lengthInput: {
|
lengthInput: {
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "lightgrey",
|
||||||
|
borderStyle: "solid",
|
||||||
},
|
},
|
||||||
widthInput: {
|
widthInput: {
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: "lightgrey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
},
|
||||||
|
productListHeader: {
|
||||||
|
flexDirection: "row",
|
||||||
|
padding: 5,
|
||||||
},
|
},
|
||||||
productNameText: {
|
productNameText: {
|
||||||
paddingLeft: 10,
|
paddingLeft: 10,
|
||||||
|
@ -1,47 +1,46 @@
|
|||||||
import { FlatList, StyleSheet, Text, TouchableHighlight } from "react-native";
|
import { FlatList, ScrollView, StyleSheet, Text, TouchableHighlight } from "react-native";
|
||||||
import { ProductTile } from "./ProductTile";
|
import { ProductTile } from "./ProductTile";
|
||||||
import { Product } from "@/lib/product";
|
import { Id, Product } from "@/lib/product";
|
||||||
import { useState } from "react";
|
import { Key, useEffect, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { selectProducts } from "@/features/product/productSlice";
|
import { selectProducts } from "@/features/product/productSlice";
|
||||||
|
import { useAppSelector } from "@/app/store";
|
||||||
|
|
||||||
export type ProductSelectionProps = {
|
export type ProductSelectionProps = {
|
||||||
onProductSelected?: (product : Product) => any;
|
onProductSelected?: (product: Product) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProductList ({onProductSelected} : ProductSelectionProps) {
|
export default function ProductList({ onProductSelected }: ProductSelectionProps) {
|
||||||
|
|
||||||
const products = useSelector(selectProducts);
|
const [activeProduct, setActiveProduct] = useState(null as null | Product);
|
||||||
|
const products = useAppSelector(selectProducts).filter(p => (!!p.dimensions));
|
||||||
|
|
||||||
const [activeProduct, setActiveProduct] = useState(null as null | Product);
|
function doOnProductSelected(product: Product) {
|
||||||
|
setActiveProduct(product);
|
||||||
|
onProductSelected && onProductSelected(product);
|
||||||
|
}
|
||||||
|
|
||||||
function doOnProductSelected(product : Product) {
|
return (
|
||||||
setActiveProduct(product);
|
<ScrollView scrollToOverflowEnabled={true}>
|
||||||
onProductSelected && onProductSelected(product);
|
{products.map(product => {
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
data={products}
|
|
||||||
style={styles.productSelectorFlatList}
|
|
||||||
renderItem={({ item }) => {
|
|
||||||
return (
|
return (
|
||||||
<ProductTile
|
<ProductTile
|
||||||
product={item}
|
product={product}
|
||||||
onProductSelected={doOnProductSelected}
|
onProductSelected={doOnProductSelected}
|
||||||
isActive={activeProduct === item}
|
isActive={activeProduct === product}
|
||||||
/>
|
key={product.id}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} } />
|
})}
|
||||||
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
||||||
productSelectorFlatList: {
|
productSelectorFlatList: {
|
||||||
padding: 10,
|
padding: 10,
|
||||||
margin: 10,
|
margin: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
@ -6,49 +6,50 @@ export type OnProductSelectedFunc = (product : Product) => any;
|
|||||||
|
|
||||||
type MyStyle = StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>;
|
type MyStyle = StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>;
|
||||||
|
|
||||||
|
type StyleSpec = {
|
||||||
|
highlight?: MyStyle,
|
||||||
|
text?: MyStyle,
|
||||||
|
image?: MyStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ProductTileProps = {
|
export type ProductTileProps = {
|
||||||
product: (Product),
|
product: (Product),
|
||||||
onProductSelected?: OnProductSelectedFunc,
|
onProductSelected?: OnProductSelectedFunc,
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
style?: {
|
|
||||||
default?: {
|
|
||||||
highlight?: MyStyle,
|
|
||||||
text?: MyStyle,
|
|
||||||
image?: MyStyle,
|
|
||||||
}
|
|
||||||
active?: {
|
|
||||||
highlight?: MyStyle,
|
|
||||||
text?: MyStyle,
|
|
||||||
image?: MyStyle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FALLBACK_IMAGE = "";
|
const FALLBACK_IMAGE = "";
|
||||||
|
|
||||||
export function ProductTile ({product, onProductSelected, isActive, style} : ProductTileProps) {
|
export function ProductTile ({product, onProductSelected, isActive} : ProductTileProps) {
|
||||||
const _style = (isActive ? style?.active : style?.default) || {};
|
const k = isActive ? "active" : "default";
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<TouchableHighlight
|
<TouchableHighlight
|
||||||
style={_style.highlight || styles.highlight}
|
style={styles[k].highlight}
|
||||||
onPress={() => onProductSelected && onProductSelected(product)}>
|
onPress={() => onProductSelected && onProductSelected(product)}>
|
||||||
<Text style={_style.text || styles.text}>{product.attributes.name || `Product ${product.id}`} ({product.pricePerUnitDisplay})</Text>
|
<Text style={styles[k].text}>{product.attributes.name || `Product ${product.id}`} ({product.pricePerUnitDisplay})</Text>
|
||||||
</TouchableHighlight>
|
</TouchableHighlight>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = {
|
||||||
highlight: {
|
active: StyleSheet.create({
|
||||||
|
highlight: {
|
||||||
},
|
padding: 10,
|
||||||
image: {
|
margin: 2,
|
||||||
|
color: "lightblue",
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
}
|
||||||
},
|
}),
|
||||||
tile: {
|
default: StyleSheet.create({
|
||||||
|
highlight: {
|
||||||
},
|
padding: 10,
|
||||||
})
|
margin: 2,
|
||||||
|
backgroundColor: "lightgrey",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
@ -13,7 +13,7 @@ export default function UnitChooser({ choices, onChoicePressed, activeColor, def
|
|||||||
const [value, setValue] = useState(choices[0] as Length);
|
const [value, setValue] = useState(choices[0] as Length);
|
||||||
|
|
||||||
activeColor = activeColor || "lightblue";
|
activeColor = activeColor || "lightblue";
|
||||||
defaultColor = activeColor || "lightgrey";
|
defaultColor = defaultColor || "lightgrey";
|
||||||
|
|
||||||
function doChoiceClicked(choice: Length) {
|
function doChoiceClicked(choice: Length) {
|
||||||
setValue(choice);
|
setValue(choice);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { render, fireEvent, screen, act } from '@testing-library/react-native';
|
import { render, fireEvent, screen, act, within } from '@testing-library/react-native';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import ProductCalculatorSelector from '@/components/ProductCalculatorSelector';
|
import ProductCalculatorSelector from '@/components/ProductCalculatorSelector';
|
||||||
import { renderWithProviders } from '@/lib/rendering';
|
import { renderWithProviders } from '@/lib/rendering';
|
||||||
@ -61,10 +61,12 @@ describe('ProductCalculatorSelector', () => {
|
|||||||
fireEvent.changeText(widthInput, "4");
|
fireEvent.changeText(widthInput, "4");
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.advanceTimersByTime(500);
|
jest.advanceTimersByTime(3000);
|
||||||
|
|
||||||
const price = mockAreaProduct.priceFor({l: 2, w: 4, u: "ft"});
|
const price = mockAreaProduct.priceFor({l: 2, w: 4, u: "ft"});
|
||||||
const sPrice = price.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 2,});
|
const sPrice = price.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 2,});
|
||||||
expect(screen.getByLabelText("calculated price").find().toBeTruthy();
|
const element = screen.getByLabelText("calculated price");
|
||||||
|
const {getByText} = within(element);
|
||||||
|
expect(getByText(sPrice)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -146,10 +146,14 @@ const productsState = createSlice({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const selectProducts = (state: RootState) => {
|
export const selectProductsDatas = (state: RootState) => {
|
||||||
return state.products.map(obj => Product.fromObject(obj));
|
return state.products;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const selectProducts = createSelector([selectProductsDatas], productsData => {
|
||||||
|
return productsData.map(d => Product.fromObject(d));
|
||||||
|
})
|
||||||
|
|
||||||
export const selectProductIds = createSelector([selectProducts], products => {
|
export const selectProductIds = createSelector([selectProducts], products => {
|
||||||
return products.map(p => p.id);
|
return products.map(p => p.id);
|
||||||
})
|
})
|
||||||
|
@ -66,7 +66,7 @@ export function dimensionArea(d: dimensions_t) {
|
|||||||
|
|
||||||
export class Product {
|
export class Product {
|
||||||
|
|
||||||
public id? : Id;
|
public id?: Id;
|
||||||
|
|
||||||
constructor(public pricePerUnit: number, public dimensions: dimensions_t, public attributes: ProductAttributes = {},
|
constructor(public pricePerUnit: number, public dimensions: dimensions_t, public attributes: ProductAttributes = {},
|
||||||
id?: Id,
|
id?: Id,
|
||||||
@ -74,12 +74,13 @@ export class Product {
|
|||||||
this.id = id || uuid.v4().toString();
|
this.id = id || uuid.v4().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public priceFor(dimensions: dimensions_t): number {
|
public priceFor(dimensions: dimensions_t, damage : number): number {
|
||||||
|
if (Number.isNaN(damage)) damage = 0;
|
||||||
const dim = matchDimensions(dimensions, this.dimensions);
|
const dim = matchDimensions(dimensions, this.dimensions);
|
||||||
return (
|
return (
|
||||||
dim.w ? dimensionArea(dim) / dimensionArea(this.dimensions) * this.pricePerUnit
|
dim.w ? dimensionArea(dim) / dimensionArea(this.dimensions) * this.pricePerUnit
|
||||||
: (dim.l / this.dimensions.l) * this.pricePerUnit
|
: (dim.l / this.dimensions.l) * this.pricePerUnit
|
||||||
)
|
) * (1.0 - damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
get priceDisplay() {
|
get priceDisplay() {
|
||||||
@ -91,9 +92,9 @@ export class Product {
|
|||||||
|
|
||||||
get pricePerUnitDisplay() {
|
get pricePerUnitDisplay() {
|
||||||
const p = this.priceDisplay;
|
const p = this.priceDisplay;
|
||||||
const {l, u} = this.dimensions;
|
const { l, u } = this.dimensions;
|
||||||
const w = (this.dimensions as area_t).w || null;
|
const w = (this.dimensions as area_t).w || null;
|
||||||
const d = w ? `${l}${u} x ${l}${u}` : `${l}${u}`;
|
const d = w ? `${l}${u} x ${w}${u}` : `${l}${u}`;
|
||||||
return `$${p} per ${d}`
|
return `$${p} per ${d}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,16 +114,16 @@ export class Product {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get asObject() : ProductData {
|
get asObject(): ProductData {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
pricePerUnit: this.pricePerUnit,
|
pricePerUnit: this.pricePerUnit,
|
||||||
dimensions: JSON.parse(JSON.stringify(this.dimensions)),
|
dimensions: this.dimensions,
|
||||||
attributes: this.attributes,
|
attributes: this.attributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromObject({id, pricePerUnit, dimensions, attributes} : ProductData) {
|
static fromObject({ id, pricePerUnit, dimensions, attributes }: ProductData) {
|
||||||
return new Product(
|
return new Product(
|
||||||
pricePerUnit,
|
pricePerUnit,
|
||||||
dimensions,
|
dimensions,
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android --offline",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web --offline",
|
"web": "expo start --web --offline",
|
||||||
"test": "jest --watchAll",
|
"test": "jest --watchAll",
|
||||||
@ -15,9 +15,11 @@
|
|||||||
"@babel/runtime": "^7.24.7",
|
"@babel/runtime": "^7.24.7",
|
||||||
"@expo/vector-icons": "^14.0.2",
|
"@expo/vector-icons": "^14.0.2",
|
||||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
"@react-native-async-storage/async-storage": "^1.23.1",
|
||||||
|
"@react-native-community/slider": "^4.5.2",
|
||||||
"@react-native/assets-registry": "^0.74.84",
|
"@react-native/assets-registry": "^0.74.84",
|
||||||
"@react-navigation/native": "^6.1.17",
|
"@react-navigation/native": "^6.1.17",
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
|
"@slider": "link:@react-native-community/@slider",
|
||||||
"@testing-library/react-native": "^12.5.1",
|
"@testing-library/react-native": "^12.5.1",
|
||||||
"@types/js-quantities": "^1.6.6",
|
"@types/js-quantities": "^1.6.6",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
@ -38,6 +40,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-native": "0.74.2",
|
"react-native": "0.74.2",
|
||||||
|
"react-native-element-dropdown": "^2.12.1",
|
||||||
"react-native-flex-grid": "^1.0.4",
|
"react-native-flex-grid": "^1.0.4",
|
||||||
"react-native-gesture-handler": "~2.16.2",
|
"react-native-gesture-handler": "~2.16.2",
|
||||||
"react-native-reanimated": "~3.10.1",
|
"react-native-reanimated": "~3.10.1",
|
||||||
|
@ -14,6 +14,9 @@ dependencies:
|
|||||||
'@react-native-async-storage/async-storage':
|
'@react-native-async-storage/async-storage':
|
||||||
specifier: ^1.23.1
|
specifier: ^1.23.1
|
||||||
version: 1.23.1(react-native@0.74.2)
|
version: 1.23.1(react-native@0.74.2)
|
||||||
|
'@react-native-community/slider':
|
||||||
|
specifier: ^4.5.2
|
||||||
|
version: 4.5.2
|
||||||
'@react-native/assets-registry':
|
'@react-native/assets-registry':
|
||||||
specifier: ^0.74.84
|
specifier: ^0.74.84
|
||||||
version: 0.74.84
|
version: 0.74.84
|
||||||
@ -23,6 +26,9 @@ dependencies:
|
|||||||
'@reduxjs/toolkit':
|
'@reduxjs/toolkit':
|
||||||
specifier: ^2.2.5
|
specifier: ^2.2.5
|
||||||
version: 2.2.5(react-redux@9.1.2)(react@18.2.0)
|
version: 2.2.5(react-redux@9.1.2)(react@18.2.0)
|
||||||
|
'@slider':
|
||||||
|
specifier: link:@react-native-community/@slider
|
||||||
|
version: link:@react-native-community/@slider
|
||||||
'@testing-library/react-native':
|
'@testing-library/react-native':
|
||||||
specifier: ^12.5.1
|
specifier: ^12.5.1
|
||||||
version: 12.5.1(jest@29.7.0)(react-native@0.74.2)(react-test-renderer@18.2.0)(react@18.2.0)
|
version: 12.5.1(jest@29.7.0)(react-native@0.74.2)(react-test-renderer@18.2.0)(react@18.2.0)
|
||||||
@ -83,6 +89,9 @@ dependencies:
|
|||||||
react-native:
|
react-native:
|
||||||
specifier: 0.74.2
|
specifier: 0.74.2
|
||||||
version: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7)(@types/react@18.2.79)(react@18.2.0)
|
version: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7)(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
react-native-element-dropdown:
|
||||||
|
specifier: ^2.12.1
|
||||||
|
version: 2.12.1(react-native@0.74.2)(react@18.2.0)
|
||||||
react-native-flex-grid:
|
react-native-flex-grid:
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4(react-native@0.74.2)(react@18.2.0)
|
version: 1.0.4(react-native@0.74.2)(react@18.2.0)
|
||||||
@ -2718,6 +2727,10 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@react-native-community/slider@4.5.2:
|
||||||
|
resolution: {integrity: sha512-DbFyCyI7rwl0FkBkp0lzEVp+5mNfS5qU/nM2sK2aSguWhj0Odkt1aKHP2iW/ljruOhgS/O4dEixXlne4OdZJDQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@react-native/assets-registry@0.74.84:
|
/@react-native/assets-registry@0.74.84:
|
||||||
resolution: {integrity: sha512-dzUhwyaX04QosWZ8zyaaNB/WYZIdeDN1lcpfQbqiOhZJShRH+FLTDVONE/dqlMQrP+EO7lDqF0RrlIt9lnOCQQ==}
|
resolution: {integrity: sha512-dzUhwyaX04QosWZ8zyaaNB/WYZIdeDN1lcpfQbqiOhZJShRH+FLTDVONE/dqlMQrP+EO7lDqF0RrlIt9lnOCQQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -8771,6 +8784,18 @@ packages:
|
|||||||
/react-is@18.3.1:
|
/react-is@18.3.1:
|
||||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||||
|
|
||||||
|
/react-native-element-dropdown@2.12.1(react-native@0.74.2)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Z3uWNFBoezDEsy9AZJxoDc9DxoAdfeprUjaInmbuzYOk6R0Y0UZ659JIalX20XNvrNRWJUfSZwbM94jWYNsIyw==}
|
||||||
|
engines: {node: '>= 16.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
dependencies:
|
||||||
|
lodash: 4.17.21
|
||||||
|
react: 18.2.0
|
||||||
|
react-native: 0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7)(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-native-flex-grid@1.0.4(react-native@0.74.2)(react@18.2.0):
|
/react-native-flex-grid@1.0.4(react-native@0.74.2)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-VFadQy3JpgBM2fNsn7W/TdebZ0JNeZgedxPJ0Xi6o+HQJU8j43YGUsGot72rEMWlzaAJCGQnMQXkW9vX+E2n5w==}
|
resolution: {integrity: sha512-VFadQy3JpgBM2fNsn7W/TdebZ0JNeZgedxPJ0Xi6o+HQJU8j43YGUsGot72rEMWlzaAJCGQnMQXkW9vX+E2n5w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
Loading…
Reference in New Issue
Block a user