2024-07-31 19:01:45 +02:00
|
|
|
import { Id, Product, product_type_t } from "@/lib/product";
|
|
|
|
import { useState } from "react";
|
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
FlatList,
|
|
|
|
StyleSheet,
|
|
|
|
Text,
|
|
|
|
TextInput,
|
|
|
|
Touchable,
|
|
|
|
TouchableHighlight,
|
|
|
|
View,
|
|
|
|
} from "react-native";
|
2024-06-29 02:04:30 +02:00
|
|
|
import { ProductAttributeEditor } from "./ProductAttributeEditor";
|
2024-07-31 19:01:45 +02:00
|
|
|
import { Dropdown } from "react-native-element-dropdown";
|
2024-06-30 18:37:27 +02:00
|
|
|
import { Ionicons } from "@expo/vector-icons";
|
2024-07-01 21:23:45 +02:00
|
|
|
import { Length } from "convert";
|
2024-07-31 19:01:45 +02:00
|
|
|
import { dimensions_t } from "@/lib/dimensions";
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-06-30 18:37:27 +02:00
|
|
|
export type ProductAddedFunc = () => any;
|
|
|
|
export type ProductDeletedFunc = (product_id: Id) => any;
|
|
|
|
export type AttributeAddedFunc = (product_id: Id) => any;
|
2024-07-31 19:01:45 +02:00
|
|
|
export type AttributeKeyUpdatedFunc = (
|
|
|
|
product_id: Id,
|
|
|
|
oldKey: string,
|
|
|
|
newKey: string
|
|
|
|
) => any;
|
|
|
|
export type AttributeUpdatedFunc = (
|
|
|
|
product_id: Id,
|
|
|
|
attribute: string,
|
|
|
|
value: string
|
|
|
|
) => any;
|
2024-06-30 18:37:27 +02:00
|
|
|
export type AttributeDeletedFunc = (product_id: Id, attribute: string) => any;
|
|
|
|
export type PriceUpdatedFunc = (product_id: Id, price: number) => any;
|
2024-07-31 19:01:45 +02:00
|
|
|
export type DimensionUpdatedFunc = (
|
|
|
|
product_id: Id,
|
|
|
|
dimension: dimensions_t
|
|
|
|
) => any;
|
|
|
|
export type ProductTypeChangedFunc = (
|
|
|
|
product_id: Id,
|
|
|
|
product_type: product_type_t
|
|
|
|
) => any;
|
2024-06-27 23:31:59 +02:00
|
|
|
|
|
|
|
export type ProductEditorItemProps = {
|
2024-07-31 19:01:45 +02:00
|
|
|
product: Product;
|
|
|
|
onProductAdded?: ProductAddedFunc;
|
|
|
|
onProductDeleted?: ProductDeletedFunc;
|
|
|
|
onAttributeAdded?: AttributeAddedFunc;
|
|
|
|
onAttributeKeyChanged?: AttributeKeyUpdatedFunc;
|
|
|
|
onAttributeUpdated?: AttributeUpdatedFunc;
|
|
|
|
onAttributeDeleted?: AttributeDeletedFunc;
|
|
|
|
onPriceUpdated?: PriceUpdatedFunc;
|
|
|
|
onDimensionsUpdated?: DimensionUpdatedFunc;
|
|
|
|
onProductTypeChanged?: ProductTypeChangedFunc;
|
|
|
|
};
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-06-30 18:37:27 +02:00
|
|
|
export const ProductEditorItem = (props: ProductEditorItemProps) => {
|
2024-07-31 19:01:45 +02:00
|
|
|
const [showAttributes, setShowAttributes] = useState(false);
|
|
|
|
const product = props.product;
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onProductTypeChange(id: Id, newProductType: product_type_t) {
|
|
|
|
props.onProductTypeChanged &&
|
|
|
|
props.onProductTypeChanged(product.id as Id, newProductType);
|
|
|
|
}
|
2024-06-29 02:04:30 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onAttributeChanged(key: string, newValue: string) {
|
|
|
|
props.onAttributeUpdated &&
|
|
|
|
props.onAttributeUpdated(product.id as Id, key, newValue);
|
|
|
|
}
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onAttributeKeyChanged(oldKey: string, newKey: string) {
|
|
|
|
props.onAttributeKeyChanged &&
|
|
|
|
props.onAttributeKeyChanged(product.id as Id, oldKey, newKey);
|
|
|
|
}
|
2024-06-30 18:37:27 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onAttributeDelete(key: string) {
|
|
|
|
props.onAttributeDeleted && props.onAttributeDeleted(product.id as Id, key);
|
|
|
|
}
|
2024-06-30 18:37:27 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onPricePerUnitChange(pricePerUnit: string) {
|
|
|
|
props.onPriceUpdated &&
|
|
|
|
props.onPriceUpdated(
|
|
|
|
product.id as Id,
|
|
|
|
parseFloat(pricePerUnit) || parseInt(pricePerUnit)
|
|
|
|
);
|
|
|
|
}
|
2024-06-30 18:37:27 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onUnitsChanged(newUnits: Length) {
|
|
|
|
props.onDimensionsUpdated &&
|
|
|
|
props.onDimensionsUpdated(product.id as Id, {
|
|
|
|
...(product.dimensions as dimensions_t),
|
|
|
|
u: newUnits,
|
|
|
|
});
|
|
|
|
}
|
2024-06-30 18:37:27 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onChangeLength(len: string) {
|
|
|
|
const l = parseFloat(len) || parseInt(len);
|
|
|
|
props.onDimensionsUpdated &&
|
|
|
|
props.onDimensionsUpdated(product.id as Id, {
|
|
|
|
...(product.dimensions as dimensions_t),
|
|
|
|
l,
|
|
|
|
});
|
|
|
|
}
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onChangeWidth(width: string) {
|
|
|
|
const w = width.length == 0 ? null : parseFloat(width) || parseInt(width);
|
|
|
|
props.onDimensionsUpdated &&
|
|
|
|
props.onDimensionsUpdated(product.id as Id, {
|
|
|
|
...(product.dimensions as dimensions_t),
|
|
|
|
...(w ? { w } : {}),
|
|
|
|
});
|
|
|
|
}
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
function onDeleteProduct() {
|
|
|
|
props.onProductDeleted && props.onProductDeleted(product.id as Id);
|
|
|
|
}
|
2024-06-27 23:31:59 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
const length = new String(
|
|
|
|
product.dimensions.l || product.dimensions.l || "0"
|
|
|
|
) as string;
|
|
|
|
const width = new String(product.dimensions.w || "") as string;
|
|
|
|
const dimension = product.dimensions.u || product.dimensions.u || "foot";
|
2024-06-30 18:37:27 +02:00
|
|
|
|
2024-07-31 19:01:45 +02:00
|
|
|
return (
|
|
|
|
<View>
|
|
|
|
<View style={styles.productListHeader}>
|
|
|
|
<TouchableHighlight
|
|
|
|
onPress={() => setShowAttributes(!showAttributes)}
|
|
|
|
aria-label="Product Item"
|
|
|
|
style={styles.productItemName}
|
|
|
|
>
|
|
|
|
{product.attributes && (
|
|
|
|
<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" />
|
|
|
|
</TouchableHighlight>
|
|
|
|
</View>
|
|
|
|
{showAttributes && (
|
|
|
|
<View style={styles.detailsWrapper}>
|
|
|
|
<View style={styles.priceSpecWrapper}>
|
|
|
|
<Text style={styles.priceLabel}>$</Text>
|
|
|
|
<TextInput
|
|
|
|
inputMode="decimal"
|
|
|
|
defaultValue={new String(product.pricePerUnit).valueOf()}
|
|
|
|
aria-label="price per unit"
|
|
|
|
onChangeText={onPricePerUnitChange}
|
|
|
|
style={styles.priceInput}
|
|
|
|
/>
|
|
|
|
<Text style={styles.per}>per</Text>
|
|
|
|
<Dropdown
|
|
|
|
data={[
|
|
|
|
{ label: "feet", value: "ft" },
|
|
|
|
{ label: "inches", value: "in" },
|
|
|
|
]}
|
|
|
|
style={styles.unitsSelect}
|
|
|
|
mode="modal"
|
|
|
|
labelField="label"
|
|
|
|
valueField="value"
|
|
|
|
value={product.dimensions.u || "ft"}
|
|
|
|
onChange={(item) => onUnitsChanged(item.value as Length)}
|
|
|
|
/>
|
|
|
|
<TextInput
|
|
|
|
inputMode="decimal"
|
|
|
|
defaultValue={length}
|
|
|
|
onChangeText={onChangeLength}
|
|
|
|
style={styles.lengthInput}
|
|
|
|
aria-label="length"
|
|
|
|
/>
|
|
|
|
<Text style={{ flex: 1 }}>x</Text>
|
|
|
|
<TextInput
|
|
|
|
inputMode="decimal"
|
|
|
|
defaultValue={width}
|
|
|
|
onChangeText={onChangeWidth}
|
|
|
|
style={styles.widthInput}
|
|
|
|
aria-label="width"
|
|
|
|
/>
|
|
|
|
</View>
|
|
|
|
<Button
|
|
|
|
title="+ Add Attribute"
|
|
|
|
onPress={() =>
|
|
|
|
props.onAttributeAdded && props.onAttributeAdded(product.id as Id)
|
2024-06-27 23:31:59 +02:00
|
|
|
}
|
2024-07-31 19:01:45 +02:00
|
|
|
/>
|
|
|
|
{product.attributes && (
|
|
|
|
<FlatList
|
|
|
|
style={styles.productAttributesList}
|
|
|
|
data={Object.entries(product.attributes)}
|
|
|
|
renderItem={({ item }) => (
|
|
|
|
<ProductAttributeEditor
|
|
|
|
onProductTypeChange={onProductTypeChange}
|
|
|
|
attributeKey={item[0] || "some key"}
|
|
|
|
attributeValue={item[1]}
|
|
|
|
onChangeAttributeKey={onAttributeKeyChanged}
|
|
|
|
onChangeAttribute={onAttributeChanged}
|
|
|
|
onDelete={onAttributeDelete}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
keyExtractor={(item, i) => `${product.id}-${i}`}
|
|
|
|
/>
|
|
|
|
)}
|
2024-06-28 04:00:29 +02:00
|
|
|
</View>
|
2024-07-31 19:01:45 +02:00
|
|
|
)}
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
};
|
2024-06-27 23:31:59 +02:00
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2024-07-31 19:01:45 +02:00
|
|
|
deleteProductHighlight: {
|
|
|
|
padding: 5,
|
|
|
|
borderWidth: 1,
|
|
|
|
},
|
|
|
|
deleteProductButton: {
|
|
|
|
fontSize: 20,
|
|
|
|
},
|
|
|
|
detailsWrapper: {},
|
|
|
|
priceSpecWrapper: {
|
|
|
|
flexDirection: "row",
|
|
|
|
},
|
|
|
|
priceLabel: {},
|
|
|
|
priceInput: {
|
|
|
|
flex: 1,
|
|
|
|
borderWidth: 2,
|
|
|
|
borderColor: "lightgrey",
|
|
|
|
borderStyle: "solid",
|
|
|
|
},
|
|
|
|
per: {
|
|
|
|
padding: 5,
|
|
|
|
},
|
|
|
|
unitsLabel: {},
|
|
|
|
unitsSelect: {
|
|
|
|
flex: 1,
|
|
|
|
padding: 5,
|
|
|
|
},
|
|
|
|
lengthInput: {
|
|
|
|
flex: 1,
|
|
|
|
borderWidth: 2,
|
|
|
|
borderColor: "lightgrey",
|
|
|
|
borderStyle: "solid",
|
|
|
|
},
|
|
|
|
widthInput: {
|
|
|
|
flex: 1,
|
|
|
|
borderWidth: 2,
|
|
|
|
borderColor: "lightgrey",
|
|
|
|
borderStyle: "solid",
|
|
|
|
},
|
|
|
|
productListHeader: {
|
|
|
|
flexDirection: "row",
|
|
|
|
padding: 5,
|
|
|
|
},
|
|
|
|
productNameText: {
|
|
|
|
paddingLeft: 10,
|
|
|
|
paddingRight: 10,
|
|
|
|
},
|
|
|
|
productItemName: {
|
|
|
|
flex: 1,
|
|
|
|
backgroundColor: "lightgrey",
|
|
|
|
padding: 4,
|
|
|
|
margin: 4,
|
|
|
|
},
|
|
|
|
productAttributesList: {
|
|
|
|
margin: 10,
|
|
|
|
padding: 10,
|
|
|
|
borderWidth: 1,
|
|
|
|
borderStyle: "solid",
|
|
|
|
borderColor: "black",
|
|
|
|
},
|
|
|
|
});
|