diff --git a/backend/zato-csm-backend/config/supabase.py b/backend/zato-csm-backend/config/supabase.py index 34afeb60..ec75fb65 100644 --- a/backend/zato-csm-backend/config/supabase.py +++ b/backend/zato-csm-backend/config/supabase.py @@ -3,7 +3,10 @@ from dotenv import load_dotenv from fastapi import HTTPException -load_dotenv() +load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env")) +# print("SUPABASE_URL:", os.getenv("SUPABASE_URL")) +# print("SUPABASE_SERVICE_KEY:", os.getenv("SUPABASE_SERVICE_KEY")) + SUPABASE_URL = os.getenv("SUPABASE_URL") SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY") diff --git a/backend/zato-csm-backend/main.py b/backend/zato-csm-backend/main.py index c508257e..9936c5f1 100644 --- a/backend/zato-csm-backend/main.py +++ b/backend/zato-csm-backend/main.py @@ -10,7 +10,6 @@ # --- Swagger Bearer Token Support --- from fastapi.openapi.utils import get_openapi - def custom_openapi(): if app.openapi_schema: return app.openapi_schema diff --git a/backend/zato-csm-backend/requirements.txt b/backend/zato-csm-backend/requirements.txt index c6914a66..078727d5 100644 --- a/backend/zato-csm-backend/requirements.txt +++ b/backend/zato-csm-backend/requirements.txt @@ -51,4 +51,4 @@ supabase_auth==2.12.3 supabase_functions==0.10.1 urllib3==2.5.0 websockets==15.0.1 -cloudinary==1.44.0 \ No newline at end of file +cloudinary==1.44.0 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index a37635b0..f683043a 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -49,3 +49,4 @@ --sidebar-border: #f1c658; --sidebar-ring: #e28e18; } + diff --git a/frontend/src/app/new-product/page.tsx b/frontend/src/app/new-product/page.tsx index 5f42d937..86ee1c86 100644 --- a/frontend/src/app/new-product/page.tsx +++ b/frontend/src/app/new-product/page.tsx @@ -7,6 +7,9 @@ import ImagesUploader from '@/components/new-product/ImagesUploader'; import NewProductForm from '@/components/new-product/NewProductForm'; import { useAuth } from '@/context/auth-store'; import { productsAPI } from '@/services/api.service'; +import { Formik, Form } from 'formik'; +import * as Yup from 'yup'; +import ProductInfoForm from '@/components/new-product/ProductInfoForm'; const NewProductPage: React.FC = () => { const router = useRouter(); @@ -91,8 +94,37 @@ const NewProductPage: React.FC = () => { } }; + const validationSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + unit: Yup.string().required('Unit is required'), + price: Yup.number() + .typeError('Price must be a number') + .positive('Price must be greater than 0') + .required('Price is required'), + inventoryQuantity: Yup.number() + .typeError('Inventory must be a number') + .integer('Inventory must be an integer') + .min(0, 'Inventory must be 0 or more') + .required('Inventory is required'), + category: Yup.string().required('Category is required'), + }); + + const initialValues = { + productType: 'Physical Product', + name: '', + description: '', + location: '', + unit: 'Per item', + weight: '', + price: '', + inventoryQuantity: '', + lowStockAlert: '', + sku: '', + category: '', + }; + return ( -
+
router.push('/inventory')} onSave={() => setSubmitSignal((s) => s + 1)} @@ -100,38 +132,41 @@ const NewProductPage: React.FC = () => { error={error} /> -
-
-
- - - -
- -
-
- -
- Set location in the form on the left. +
+ + {(formik) => ( +
+
+
+ + + +
+ +
-
-
-
+ + )} +
); }; export default NewProductPage; + diff --git a/frontend/src/components/new-product/Header.tsx b/frontend/src/components/new-product/Header.tsx index a01abf14..b4702e77 100644 --- a/frontend/src/components/new-product/Header.tsx +++ b/frontend/src/components/new-product/Header.tsx @@ -1,5 +1,6 @@ import React from "react"; import { FaRegFolder } from "react-icons/fa6"; +import { IoMdArrowRoundBack } from "react-icons/io"; type Props = { onBack: () => void; @@ -10,34 +11,25 @@ type Props = { const Header: React.FC = ({ onBack, onSave, saving, error }) => { return ( -
-
- -

- New Product -

-
+
+
+ {/* Botón siempre visible */} + + + {/* Título con margen responsivo */} +

+ New Product +

+
-
-
- Status: +
+
+ Status: Active
@@ -45,26 +37,24 @@ const Header: React.FC = ({ onBack, onSave, saving, error }) => { diff --git a/frontend/src/components/new-product/NewProductForm.tsx b/frontend/src/components/new-product/NewProductForm.tsx index 699278c0..d6d44d82 100644 --- a/frontend/src/components/new-product/NewProductForm.tsx +++ b/frontend/src/components/new-product/NewProductForm.tsx @@ -1,289 +1,124 @@ "use client"; -import React, { useEffect, useRef } from "react"; -import { Formik, Form, Field, ErrorMessage, FormikHelpers } from "formik"; -import * as Yup from "yup"; - -type Values = { - productType: string; - name: string; - description: string; - location: string; - unit: string; - weight: string; - price: string; - inventoryQuantity: string; - lowStockAlert: string; - sku: string; - category: string; -}; - -type Option = { label: string; value: string }; +import React from "react"; +import { Field, ErrorMessage } from "formik"; type Props = { - initialValues?: Partial; - existingCategories: string[]; - unitOptions: Option[]; - productTypeOptions: Option[]; - onSubmit: (values: Values) => void; - submitSignal: number; + formik: any; + unitOptions: { label: string; value: string }[]; + productTypeOptions: { label: string; value: string }[]; }; -const validationSchema = Yup.object().shape({ - name: Yup.string().required("Name is required"), - unit: Yup.string().required("Unit is required"), - price: Yup.number() - .typeError("Price must be a number") - .positive("Price must be greater than 0") - .required("Price is required"), - inventoryQuantity: Yup.number() - .typeError("Inventory must be a number") - .integer("Inventory must be an integer") - .min(0, "Inventory must be 0 or more") - .required("Inventory is required"), - category: Yup.string().required("Category is required"), -}); - -const NewProductForm: React.FC = ({ - initialValues, - existingCategories, - unitOptions, - productTypeOptions, - onSubmit, - submitSignal, -}) => { - const formikSubmitRef = useRef<(() => Promise) | null>(null); - - useEffect(() => { - if (submitSignal && formikSubmitRef.current) formikSubmitRef.current(); - }, [submitSignal]); - - const defaults: Values = { - productType: - (initialValues && initialValues.productType) || "Physical Product", - name: (initialValues && initialValues.name) || "", - description: (initialValues && initialValues.description) || "", - location: (initialValues && initialValues.location) || "", - unit: (initialValues && initialValues.unit) || "Per item", - weight: (initialValues && initialValues.weight) || "", - price: (initialValues && initialValues.price) || "", - inventoryQuantity: (initialValues && initialValues.inventoryQuantity) || "", - lowStockAlert: (initialValues && initialValues.lowStockAlert) || "", - sku: (initialValues && initialValues.sku) || "", - category: (initialValues && initialValues.category) || "", - }; - +const NewProductForm: React.FC = ({ formik, unitOptions, productTypeOptions }) => { return ( - ) => { - onSubmit(values); - helpers.setSubmitting(false); - }} - > - {(formik) => { - formikSubmitRef.current = formik.submitForm; - return ( -
-
-
- - -
- -
-
- -
- - -
+
+ +
+

Units

+
+
+ + + {unitOptions.map((opt) => ( + + ))} + +
+
- -
- - +
+ +
+ + + {productTypeOptions.map((opt) => ( + + ))} + +
+ +
+ + +
+
+
+ +
+

Inventory

+
+
+ + +
+
- -
-

- Categorization -

-
- {existingCategories.map((category) => { - const isSelected = formik.values.category === category; - return ( -
+ +
+ + +
+ +
+ + +
+
+
+ +
+ - ); - })} -
- + +
-
-
- - -
-

Units

-
-
- - - {unitOptions.map((opt) => ( - - ))} - -
- -
-
- -
- - - {productTypeOptions.map((opt) => ( - - ))} - -
- -
- - -
- -
- -
- - $ - - -
-
- -
-
-
-
- -
-

- Inventory -

-
-
- - -
- -
-
- -
- - -
- -
- - -
-
-
- - ); - }} - ); }; export default NewProductForm; + diff --git a/frontend/src/components/new-product/ProductInfoForm.tsx b/frontend/src/components/new-product/ProductInfoForm.tsx new file mode 100644 index 00000000..fc48dff6 --- /dev/null +++ b/frontend/src/components/new-product/ProductInfoForm.tsx @@ -0,0 +1,78 @@ +"use client"; + +import React from "react"; +import { Field, ErrorMessage } from "formik"; + +type Props = { + formik: any; + existingCategories: string[]; +}; + +const ProductInfoForm: React.FC = ({ formik, existingCategories }) => { + return ( +
+
+
+ + +
+ +
+
+ +
+ + +
+
+ +
+

Categorization

+
+ {existingCategories.map((category) => { + const isSelected = formik.values.category === category; + return ( + + ); + })} +
+ +
+
+
+
+ ); +}; + +export default ProductInfoForm;