From 59c90ca05914b0b2c87c7c6e5755e0e619da811d Mon Sep 17 00:00:00 2001 From: DJ Steinmetz Date: Wed, 14 Jun 2023 12:46:01 -0500 Subject: [PATCH 1/7] issue 89: wip xp ui --- .../products/detail/ProductDetail.tsx | 91 ++++++-- .../products/modals/ProductXpModal.tsx | 195 ++++++++++++++++++ 2 files changed, 269 insertions(+), 17 deletions(-) create mode 100644 src/components/products/modals/ProductXpModal.tsx diff --git a/src/components/products/detail/ProductDetail.tsx b/src/components/products/detail/ProductDetail.tsx index dc36dc5e..f3a19bed 100644 --- a/src/components/products/detail/ProductDetail.tsx +++ b/src/components/products/detail/ProductDetail.tsx @@ -14,7 +14,8 @@ import { Icon, SimpleGrid, Text, - Button + Button, + useDisclosure } from "@chakra-ui/react" import {DescriptionForm} from "./forms/DescriptionForm/DescriptionForm" import {DetailsForm} from "./forms/DetailsForm/DetailsForm" @@ -23,14 +24,14 @@ import {ShippingForm} from "./forms/ShippingForm/ShippingForm" import {UnitOfMeasureForm} from "./forms/UnitOfMeasureForm/UnitOfMeasureForm" import ImagePreview from "./ImagePreview" import {withDefaultValuesFallback, getObjectDiff, makeNestedObject} from "utils" -import {cloneDeep, invert} from "lodash" +import _, {cloneDeep, invert} from "lodash" import {PriceSchedules, Products} from "ordercloud-javascript-sdk" import {defaultValues, validationSchema} from "./forms/meta" import ProductDetailToolbar from "./ProductDetailToolbar" import {useErrorToast, useSuccessToast} from "hooks/useToast" -import {IProduct} from "types/ordercloud/IProduct" +import {IProduct, IProductXp} from "types/ordercloud/IProduct" import {useRouter} from "hooks/useRouter" -import {useState} from "react" +import {useMemo, useState} from "react" import {yupResolver} from "@hookform/resolvers/yup" import {useForm} from "react-hook-form" import {PricingForm} from "./forms/PricingForm/PricingForm" @@ -44,6 +45,9 @@ import ProductVariants from "../ProductVariants" import {FacetsForm} from "./forms/FacetsForm/FacetsForm" import {IProductFacet} from "types/ordercloud/IProductFacet" import {MediaForm} from "./forms/MediaForm/MediaForm" +import ProductXpCard from "../ProductXpCard" +import ProductXpModal from "../modals/ProductXpModal" +import {tabFieldNames} from "./forms/meta" export type ProductDetailTab = "Details" | "Pricing" | "Variants" | "Media" | "Facets" | "Customization" @@ -78,6 +82,7 @@ export default function ProductDetail({ const successToast = useSuccessToast() const errorToast = useErrorToast() const [tabIndex, setTabIndex] = useState(tabIndexMap[initialTab]) + const xpDisclosure = useDisclosure() const isCreatingNew = !Boolean(product?.ID) const initialViewVisibility: Record = { Details: true, @@ -88,6 +93,8 @@ export default function ProductDetail({ Customization: true } const [viewVisibility, setViewVisibility] = useState(initialViewVisibility) + const [xpPropertyNameToEdit, setXpPropertyNameToEdit] = useState(null) + const [xpPropertyValueToEdit, setXpPropertyValueToEdit] = useState(null) const createFormFacets = (facetList: IProductFacet[] = [], facetsOnProduct: any) => { const formattedFacets = facetList.map((facet) => { @@ -119,17 +126,17 @@ export default function ProductDetail({ ) : makeNestedObject(defaultValues) - const handleTabsChange = (index) => { - router.push({query: {...router.query, tab: inverseTabIndexMap[index]}}, undefined, {shallow: true}) - setTabIndex(index) - } - const {handleSubmit, control, reset, trigger} = useForm({ resolver: yupResolver(validationSchema), defaultValues: initialValues, mode: "onBlur" }) + const handleTabsChange = (index) => { + router.push({query: {...router.query, tab: inverseTabIndexMap[index]}}, undefined, {shallow: true}) + setTabIndex(index) + } + const generateUpdatedFacets = (facets = []) => { const updatedFacetsOnProduct = {} @@ -210,6 +217,15 @@ export default function ProductDetail({ {props.children} ) + const nonUiXp = useMemo(() => { + const uiXpFields = Object.values(tabFieldNames) + ?.flat() + ?.filter((field) => field.includes(".xp.")) + ?.map((xp) => xp?.split(".")?.at(2)) + const productXp = _.cloneDeep(product?.xp) + uiXpFields.forEach((f) => delete productXp[f]) + return productXp + }, [product]) return ( @@ -365,10 +381,10 @@ export default function ProductDetail({ - Add options like shirt text and sign verbiage to enable further product customization. + Define custom data for your products. - @@ -376,14 +392,40 @@ export default function ProductDetail({ p={6} display="flex" flexDirection={"column"} - alignItems={"center"} + alignItems={"flex-start"} justifyContent={"center"} minH={"xs"} > - - - Nothing created yet... - + {Object.values(nonUiXp)?.map((xp, idx) => { + return ( + + + + {Object.keys(nonUiXp)?.[idx]} + + {xp} + + ) + })} @@ -459,6 +501,21 @@ export default function ProductDetail({ )} )} + { + setXpPropertyNameToEdit(null) + setXpPropertyValueToEdit(null) + }} + onSuccess={(patchResponse) => { + console.log("patch response from parent", patchResponse) + product = patchResponse + }} + /> ) diff --git a/src/components/products/modals/ProductXpModal.tsx b/src/components/products/modals/ProductXpModal.tsx new file mode 100644 index 00000000..58f2a612 --- /dev/null +++ b/src/components/products/modals/ProductXpModal.tsx @@ -0,0 +1,195 @@ +import { + Alert, + Button, + ButtonGroup, + Center, + FormControl, + Heading, + HStack, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Spinner, + UseDisclosureProps +} from "@chakra-ui/react" +import {Products} from "ordercloud-javascript-sdk" +import {FC, useEffect, useState} from "react" +import {IProduct} from "types/ordercloud/IProduct" + +interface IProductXpModal { + productID: string + disclosure: UseDisclosureProps + nonUiXp: {[key: string]: any} + existingPropertyName?: string + existingPropertyValue?: string + clearExistingPropertyValues: () => void + onSuccess: (patchedProduct: IProduct) => void +} + +const ProductXpModal: FC = ({ + productID, + disclosure, + nonUiXp, + existingPropertyName, + existingPropertyValue, + clearExistingPropertyValues, + onSuccess +}) => { + const {isOpen, onClose} = disclosure + const [loading, setLoading] = useState(false) + const [propertyName, setPropertyName] = useState(existingPropertyName ?? "") + const [dataType, setDataType] = useState( + typeof existingPropertyValue == "undefined" ? "string" : typeof existingPropertyValue + ) + const [value, setValue] = useState(existingPropertyValue ?? "") + const [errors, setErrors] = useState([]) + const [isEditing, setIsEditing] = useState(false) + + const handleDataTypeChange = (type: string) => () => { + setValue("") + setDataType(type) + } + + useEffect(() => { + if (!isOpen) { + setLoading(false) + setErrors([]) + setPropertyName("") + setValue("") + clearExistingPropertyValues() + setIsEditing(false) + } + setPropertyName(existingPropertyName) + setValue(existingPropertyValue) + setDataType(typeof existingPropertyValue) + if (existingPropertyName || existingPropertyValue) setIsEditing(true) + }, [isOpen, existingPropertyName, existingPropertyValue]) + + const validate = (): string[] => { + const errors = [] + if (Object.keys(nonUiXp).includes(propertyName) && !isEditing) + errors.push(`The property name "${propertyName}" already exists on extended properties`) + if (!propertyName || propertyName === "") errors.push("You must set a property name") + if (!value || value === "") errors.push("You must set a property value") + return errors + } + + const handleSubmit = () => { + setErrors([]) + const errors = validate() + if (errors?.length > 0) { + setErrors(errors) + return + } + const request = { + xp: { + [propertyName]: value + } + } + console.log(request) + setLoading(true) + Products.Patch(productID, request) + .then((res) => { + onSuccess(res) + onClose() + }) + .catch((err) => setErrors([err])) + } + + return ( + + + + {loading && ( +
+ +
+ )} + {isEditing ? "Edit" : "New"} Extended Property + + + {errors.map((e, idx) => ( + + {e} + + ))} + + Property name + + + setPropertyName(e.target.value)} + isDisabled={isEditing} + /> + + + Property type + + + + + + + + Property value + + + setValue(e.target.value)} /> + + + + + + +
+
+ ) +} + +export default ProductXpModal From c77cff28cc55f9affdacdae07467b10245ecb10b Mon Sep 17 00:00:00 2001 From: DJ Steinmetz Date: Wed, 14 Jun 2023 14:06:59 -0500 Subject: [PATCH 2/7] issue 89: more xp ui --- .../products/detail/ProductDetail.tsx | 167 ++++++++++-------- .../products/modals/ProductXpModal.tsx | 9 +- 2 files changed, 100 insertions(+), 76 deletions(-) diff --git a/src/components/products/detail/ProductDetail.tsx b/src/components/products/detail/ProductDetail.tsx index f3a19bed..60424a92 100644 --- a/src/components/products/detail/ProductDetail.tsx +++ b/src/components/products/detail/ProductDetail.tsx @@ -24,14 +24,14 @@ import {ShippingForm} from "./forms/ShippingForm/ShippingForm" import {UnitOfMeasureForm} from "./forms/UnitOfMeasureForm/UnitOfMeasureForm" import ImagePreview from "./ImagePreview" import {withDefaultValuesFallback, getObjectDiff, makeNestedObject} from "utils" -import _, {cloneDeep, invert} from "lodash" +import {cloneDeep, invert} from "lodash" import {PriceSchedules, Products} from "ordercloud-javascript-sdk" import {defaultValues, validationSchema} from "./forms/meta" import ProductDetailToolbar from "./ProductDetailToolbar" import {useErrorToast, useSuccessToast} from "hooks/useToast" import {IProduct, IProductXp} from "types/ordercloud/IProduct" import {useRouter} from "hooks/useRouter" -import {useMemo, useState} from "react" +import {useEffect, useMemo, useState} from "react" import {yupResolver} from "@hookform/resolvers/yup" import {useForm} from "react-hook-form" import {PricingForm} from "./forms/PricingForm/PricingForm" @@ -82,6 +82,8 @@ export default function ProductDetail({ const successToast = useSuccessToast() const errorToast = useErrorToast() const [tabIndex, setTabIndex] = useState(tabIndexMap[initialTab]) + const [liveXp, setLiveXp] = useState<{[key: string]: any}>(product?.xp) + const [nonUiXp, setNonUiXp] = useState<{[key: string]: any}>({}) const xpDisclosure = useDisclosure() const isCreatingNew = !Boolean(product?.ID) const initialViewVisibility: Record = { @@ -211,22 +213,104 @@ export default function ProductDetail({ errorToast({title: "Form errors", description: "Please resolve the errors and try again."}) } + const handleXpRemoval = async (key: string) => { + const newXp = cloneDeep(liveXp) + delete newXp[key] + // First patch xp to null + await Products.Patch(product?.ID, {xp: null}) + // Then patch xp back to the state without the respective removed key + const patchedProduct = await Products.Patch(product?.ID, {xp: newXp}) + // Then set nonUiXp again with the new state. + setNonUiXp(getNonUiXp(patchedProduct.xp)) + setLiveXp(patchedProduct.xp) + } + const SimpleCard = (props: {title?: string; children: React.ReactElement}) => ( {props.title && {props.title}} {props.children} ) - const nonUiXp = useMemo(() => { + + const xpCard = (): JSX.Element => { + return ( + + + + Define custom data for your products. + + + + + + {Object.values(nonUiXp)?.map((xp, idx) => { + return ( + + + + + {Object.keys(nonUiXp)?.[idx]} + + {xp} + + ) + })} + + + + ) + } + + useEffect(() => { + const productXp = getNonUiXp(product?.xp) + setNonUiXp(productXp) + }, [product]) + + const getNonUiXp = (xp: {[key: string]: any}): {[key: string]: any} => { const uiXpFields = Object.values(tabFieldNames) - ?.flat() - ?.filter((field) => field.includes(".xp.")) - ?.map((xp) => xp?.split(".")?.at(2)) - const productXp = _.cloneDeep(product?.xp) + .flat() + .filter((field) => field.includes(".xp.")) + .map((xp) => xp?.split(".")?.at(2)) + const productXp = cloneDeep(xp) uiXpFields.forEach((f) => delete productXp[f]) return productXp - }, [product]) - + } return ( @@ -378,57 +462,7 @@ export default function ProductDetail({ )} {viewVisibility.Customization && ( - - - - Define custom data for your products. - - - - - - {Object.values(nonUiXp)?.map((xp, idx) => { - return ( - - - - {Object.keys(nonUiXp)?.[idx]} - - {xp} - - ) - })} - - - + {xpCard()} )} @@ -491,14 +525,7 @@ export default function ProductDetail({ Facets under construction )} - {viewVisibility.Customization && ( - - - Customization - - Customization under construction - - )} + {viewVisibility.Customization && xpCard()} )} { - console.log("patch response from parent", patchResponse) - product = patchResponse + setNonUiXp(getNonUiXp(patchResponse.xp)) + setLiveXp(patchResponse.xp) }} /> diff --git a/src/components/products/modals/ProductXpModal.tsx b/src/components/products/modals/ProductXpModal.tsx index 58f2a612..f2774a96 100644 --- a/src/components/products/modals/ProductXpModal.tsx +++ b/src/components/products/modals/ProductXpModal.tsx @@ -43,9 +43,7 @@ const ProductXpModal: FC = ({ const {isOpen, onClose} = disclosure const [loading, setLoading] = useState(false) const [propertyName, setPropertyName] = useState(existingPropertyName ?? "") - const [dataType, setDataType] = useState( - typeof existingPropertyValue == "undefined" ? "string" : typeof existingPropertyValue - ) + const [dataType, setDataType] = useState("string") const [value, setValue] = useState(existingPropertyValue ?? "") const [errors, setErrors] = useState([]) const [isEditing, setIsEditing] = useState(false) @@ -66,7 +64,7 @@ const ProductXpModal: FC = ({ } setPropertyName(existingPropertyName) setValue(existingPropertyValue) - setDataType(typeof existingPropertyValue) + setDataType(existingPropertyValue ? typeof existingPropertyValue : "string") if (existingPropertyName || existingPropertyValue) setIsEditing(true) }, [isOpen, existingPropertyName, existingPropertyValue]) @@ -88,10 +86,9 @@ const ProductXpModal: FC = ({ } const request = { xp: { - [propertyName]: value + [propertyName]: dataType == "number" ? Number(value) : value } } - console.log(request) setLoading(true) Products.Patch(productID, request) .then((res) => { From f5e076b63887014cda6d9f842f886deaefff0ebe Mon Sep 17 00:00:00 2001 From: DJ Steinmetz Date: Wed, 14 Jun 2023 14:11:57 -0500 Subject: [PATCH 3/7] issue 89: remove date as type since it saves as string anyway --- src/components/products/modals/ProductXpModal.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/products/modals/ProductXpModal.tsx b/src/components/products/modals/ProductXpModal.tsx index f2774a96..623ddb94 100644 --- a/src/components/products/modals/ProductXpModal.tsx +++ b/src/components/products/modals/ProductXpModal.tsx @@ -155,14 +155,6 @@ const ProductXpModal: FC = ({ > Number - Property value From e97cd793dac7acad7d38198082f8e8cef7a252b8 Mon Sep 17 00:00:00 2001 From: DJ Steinmetz Date: Thu, 15 Jun 2023 09:25:28 -0500 Subject: [PATCH 4/7] issue 89 remove unnecessary safe nav --- src/components/products/detail/ProductDetail.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/products/detail/ProductDetail.tsx b/src/components/products/detail/ProductDetail.tsx index 60424a92..25040e31 100644 --- a/src/components/products/detail/ProductDetail.tsx +++ b/src/components/products/detail/ProductDetail.tsx @@ -252,7 +252,7 @@ export default function ProductDetail({ justifyContent={"center"} minH={"xs"} > - {Object.values(nonUiXp)?.map((xp, idx) => { + {Object.values(nonUiXp).map((xp, idx) => { return ( - - - {Object.values(nonUiXp).map((xp, idx) => { - return ( - - + + + + + { - setXpPropertyNameToEdit(Object.keys(nonUiXp)[idx]) - setXpPropertyValueToEdit(xp) - xpDisclosure.onOpen() - }} - mr={3} + mr={{base: 3, md: 6}} + flexDirection={{base: "column", md: "row"}} + padding={{base: 1, md: 0}} + alignItems={{base: "flex-start", md: "center"}} + gap={2} + alignSelf="center" > - Edit - - + } + aria-label="edit" + onClick={() => { + setXpPropertyNameToEdit(Object.keys(nonUiXp)[idx]) + setXpPropertyValueToEdit(xp) + xpDisclosure.onOpen() + }} + > + Edit + + } + variant="outline" + borderColor="red.300" + color="red.300" + aria-label="delete" + onClick={() => handleXpRemoval(Object.keys(nonUiXp)[idx])} + > + Delete + + + + {Object.keys(nonUiXp)[idx]} - {xp} - - ) - })} - + + + + {xp} + + + + ) + })} ) From e2afaadc00ac47e2ffe87751efab27abcd94308b Mon Sep 17 00:00:00 2001 From: DJ Steinmetz Date: Thu, 15 Jun 2023 11:05:32 -0500 Subject: [PATCH 6/7] issue 89 cleanup instances of old xp card --- .../adminaddresses/CreateUpdateForm.tsx | 32 +- src/components/adminusers/AdminUserXpCard.tsx | 64 --- .../adminusers/CreateUpdateForm.tsx | 4 +- src/components/card/XpCard.tsx | 408 ------------------ src/components/catalogs/CatalogXpCard.tsx | 65 --- src/components/catalogs/CreateUpdateForm.tsx | 5 +- src/components/categories/CategoryXpCard.tsx | 65 --- .../categories/CreateUpdateForm.tsx | 5 +- src/components/products/ProductXpCard.tsx | 70 --- .../products/detail/ProductDetail.tsx | 66 ++- .../promotions/CreateUpdateForm.tsx | 6 +- src/components/promotions/PromotionXpCard.tsx | 64 --- src/components/users/CreateUpdateForm.tsx | 6 +- src/components/users/UserXpCard.tsx | 66 --- src/pages/products/old/[id].tsx | 6 - 15 files changed, 53 insertions(+), 879 deletions(-) delete mode 100644 src/components/adminusers/AdminUserXpCard.tsx delete mode 100644 src/components/card/XpCard.tsx delete mode 100644 src/components/catalogs/CatalogXpCard.tsx delete mode 100644 src/components/categories/CategoryXpCard.tsx delete mode 100644 src/components/products/ProductXpCard.tsx delete mode 100644 src/components/promotions/PromotionXpCard.tsx delete mode 100644 src/components/users/UserXpCard.tsx diff --git a/src/components/adminaddresses/CreateUpdateForm.tsx b/src/components/adminaddresses/CreateUpdateForm.tsx index 60adfe76..7502e449 100644 --- a/src/components/adminaddresses/CreateUpdateForm.tsx +++ b/src/components/adminaddresses/CreateUpdateForm.tsx @@ -1,30 +1,16 @@ -import * as Yup from "yup" -import { - Box, - Button, - ButtonGroup, - Card, - CardBody, - CardHeader, - Container, - Flex, - SimpleGrid, - Stack, - theme, - VStack -} from "@chakra-ui/react" -import {InputControl, SwitchControl} from "components/react-hook-form" -import {Address, AdminAddresses} from "ordercloud-javascript-sdk" -import {useRouter} from "hooks/useRouter" +import {Button, ButtonGroup, Card, CardBody, CardHeader, Container, SimpleGrid} from "@chakra-ui/react" +import {yupResolver} from "@hookform/resolvers/yup" +import {InputControl} from "components/react-hook-form" import {useCreateUpdateForm} from "hooks/useCreateUpdateForm" +import {useRouter} from "hooks/useRouter" import {pick} from "lodash" -import {IAdminAddress} from "types/ordercloud/IAdminAddress" +import {Address, AdminAddresses} from "ordercloud-javascript-sdk" import {useForm} from "react-hook-form" -import {yupResolver} from "@hookform/resolvers/yup" -import SubmitButton from "../react-hook-form/submit-button" -import ResetButton from "../react-hook-form/reset-button" import {TbChevronLeft} from "react-icons/tb" -import AdminUserXpCard from "../adminusers/AdminUserXpCard" +import {IAdminAddress} from "types/ordercloud/IAdminAddress" +import * as Yup from "yup" +import ResetButton from "../react-hook-form/reset-button" +import SubmitButton from "../react-hook-form/submit-button" export {CreateUpdateForm} interface CreateUpdateFormProps { diff --git a/src/components/adminusers/AdminUserXpCard.tsx b/src/components/adminusers/AdminUserXpCard.tsx deleted file mode 100644 index 82a9fcca..00000000 --- a/src/components/adminusers/AdminUserXpCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import {useEffect, useState} from "react" -import {AdminUsers, User} from "ordercloud-javascript-sdk" -import {useRouter} from "hooks/useRouter" -import XpCard from "../card/XpCard" -import {IAdminUser, IAdminUserXp} from "types/ordercloud/IAdminUser" - -type AdminUserDataProps = { - user: User & {xp?: unknown} -} - -export default function AdminUserXpCard({user}: AdminUserDataProps) { - const [isEditingBasicData, setIsEditingBasicData] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [isDeleting, setIsDeleting] = useState(false) - const [formValues, setFormValues] = useState(Object.assign({}, user?.xp)) - const [xpsToBeDeleted, setXpsToBeDeleted] = useState([]) - let router = useRouter() - - useEffect(() => { - setFormValues(Object.assign({}, user?.xp)) - }, [user?.xp]) - - const onUserSave = async () => { - setIsLoading(true) - if (isDeleting) { - var newUser: IAdminUser = user - delete newUser.xp - var tempXPs = Object.assign({}, formValues) - xpsToBeDeleted.forEach((e) => delete tempXPs[e]) - newUser["xp"] = tempXPs - await AdminUsers.Save(user?.ID, newUser) - setIsDeleting(false) - setXpsToBeDeleted([]) - } else { - const newUser: IAdminUser = { - ...user, - xp: formValues - } - await AdminUsers.Patch(user?.ID, newUser) - } - - setIsEditingBasicData(false) - setIsLoading(false) - router.back() - } - - return ( - <> - - - ) -} diff --git a/src/components/adminusers/CreateUpdateForm.tsx b/src/components/adminusers/CreateUpdateForm.tsx index a4f2cf41..2a9399a0 100644 --- a/src/components/adminusers/CreateUpdateForm.tsx +++ b/src/components/adminusers/CreateUpdateForm.tsx @@ -16,6 +16,7 @@ import { TableContainer, Tbody, Td, + Text, theme, Tr, VStack @@ -29,7 +30,6 @@ import {textHelper} from "utils" import {appPermissions} from "constants/app-permissions.config" import {isEqual, sortBy, difference, pick} from "lodash" import {IAdminUser} from "types/ordercloud/IAdminUser" -import AdminUserXpCard from "./AdminUserXpCard" import {useForm} from "react-hook-form" import {yupResolver} from "@hookform/resolvers/yup" import SubmitButton from "../react-hook-form/submit-button" @@ -197,7 +197,7 @@ function CreateUpdateForm({user, assignedPermissions}: CreateUpdateFormProps) { - + Under construction ) diff --git a/src/components/card/XpCard.tsx b/src/components/card/XpCard.tsx deleted file mode 100644 index b146960b..00000000 --- a/src/components/card/XpCard.tsx +++ /dev/null @@ -1,408 +0,0 @@ -import { - Heading, - Box, - Text, - Card, - Button, - HStack, - Tooltip, - Center, - Modal, - ModalBody, - ModalContent, - ModalHeader, - ModalOverlay, - useDisclosure, - Select, - Input, - Textarea, - CheckboxGroup, - Checkbox -} from "@chakra-ui/react" -import {ChangeEvent, MouseEventHandler, useEffect, useState} from "react" -import BrandedSpinner from "../branding/BrandedSpinner" -import TagContainer from "../generic/tagContainer" -import {useErrorToast} from "hooks/useToast" - -type DataProps = { - data: T & {xp?: unknown} - formValues: unknown - setFormValues: Function - isLoading: boolean - setIsLoading: Function - isEditingBasicData: boolean - setIsEditingBasicData: Function - setIsDeleting: Function - xpsToBeDeleted: string[] - setXpsToBeDeleted: Function - onSave: MouseEventHandler -} - -export default function XpCard({ - data, - formValues, - setFormValues, - isLoading, - setIsLoading, - isEditingBasicData, - setIsEditingBasicData, - setIsDeleting, - xpsToBeDeleted, - setXpsToBeDeleted, - onSave -}: DataProps) { - const {isOpen: isOpenAddXP, onOpen: onOpenAddXP, onClose: onCloseAddXP} = useDisclosure() - const {isOpen: isOpenEditXP, onOpen: onOpenEditXP, onClose: onCloseEditXP} = useDisclosure() - - const [isAdding, setIsAdding] = useState(false) - const [newXpFormName, setNewXpFormName] = useState("") - const [newXpFormType, setNewXpFormType] = useState("text") - const [newXpFormValue, setNewXpFormValue] = useState("") - const [expanded, setExpanded] = useState(true) - const [editing, setEditing] = useState("") - //const [editingType, setEditingType] = useState("text") - const errorToast = useErrorToast() - const tags = ["1", "2", "3", "4", "5", "6"] - - useEffect(() => { - setFormValues(Object.assign({}, data?.xp)) - }, [data?.xp, setFormValues]) - - const onEditClicked = (e) => { - e.preventDefault() - setFormValues(Object.assign({}, data?.xp)) - setIsEditingBasicData(true) - setExpanded(true) - } - - const onAbortClicked = (e) => { - e.preventDefault() - setIsDeleting(false) - setIsEditingBasicData(false) - setFormValues(Object.assign({}, data?.xp)) - setXpsToBeDeleted([]) - } - - const handleXPChange = (name: string) => { - setEditing(name) - setNewXpFormValue(formValues[name]) - onOpenEditXP() - } - const onEditProductXP = () => { - setIsLoading(true) - formValues[editing] = newXpFormValue - onEditProductXPClosed() - setIsLoading(false) - return - } - - const handleInputChange = - (fieldKey: string) => (e: ChangeEvent | ChangeEvent) => { - var newVal = e.target.type == "number" ? Number(e.target.value) : e.target.value - var tmpXPs = formValues - tmpXPs[fieldKey] = newVal - setFormValues(tmpXPs) - } - - const handleEditXP = (e) => { - if (editing.endsWith("###")) { - var strValues = newXpFormValue.toString() - var tempValues = strValues.includes(",") ? strValues.split(",") : [strValues] - tempValues = tempValues.includes(e.target.value) - ? tempValues.filter((ele) => ele !== e.target.value) - : [...tempValues, e.target.value] - setNewXpFormValue(tempValues.length > 1 ? tempValues.join(",") : tempValues[0]) - } else setNewXpFormValue(typeof formValues[editing] == "string" ? e.target.value : Number(e.target.value)) - } - const handleNewXPChange = (e) => { - switch (e.target.name) { - case "name": - setNewXpFormName(e.target.value) - break - case "type": - setNewXpFormType(e.target.value) - break - case "value": - if (newXpFormType == "tag") { - var strValues = newXpFormValue.toString() - var tempValues = strValues.includes(",") ? strValues.split(",") : [strValues] - tempValues = tempValues.includes(e.target.value) - ? tempValues.filter((ele) => ele !== e.target.value) - : [...tempValues, e.target.value] - setNewXpFormValue(tempValues.length > 1 ? tempValues.join(",") : tempValues[0]) - } else { - setNewXpFormValue(newXpFormType == "number" ? Number(e.target.value) : e.target.value) - } - - break - default: - return - } - } - - const onDeleteProductXPClicked = (key: string) => async (e) => { - setIsLoading(true) - setIsDeleting(true) - const tempDeleted = xpsToBeDeleted.includes(key) - ? xpsToBeDeleted.filter((thing) => thing !== key) - : [...xpsToBeDeleted, key] - setXpsToBeDeleted(tempDeleted) - setIsLoading(false) - } - - const onNewProductXP = async () => { - const TempXpFormName = newXpFormType == "tag" ? newXpFormName + "###" : newXpFormName - if (formValues[TempXpFormName] !== undefined) { - errorToast({ - title: "Validation Error", - description: "Extended property of that name already exists" - }) - return - } - setIsLoading(true) - - formValues[TempXpFormName] = newXpFormValue - onNewProductXPClosed() - setIsLoading(false) - } - const onNewProductXPClosed = async () => { - setNewXpFormName("") - setNewXpFormType("text") - setNewXpFormValue("") - - onCloseAddXP() - } - const onEditProductXPClosed = async () => { - setEditing("") - onCloseEditXP() - } - const renderCurrentSelection = () => { - switch (newXpFormType) { - case "text": - return ( - <> - Value: - - - ) - case "number": - return ( - <> - Value: - - - ) - case "tag": - return ( - <> - Value: - - {tags.map((x, key) => { - return ( - - {x} - - ) - })} - - - ) - default: - return "" - } - } - const renderCurrentEditing = () => { - const editingType = editing.endsWith("###") ? "tag" : typeof formValues[editing] == "string" ? "text" : "number" - switch (editingType) { - case "text": - if (formValues[editing].length > 60) - return ( - <> - Value: -