From f13005a031ead8d0bac51e4dba2226746c1d7b9e Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Mon, 24 Nov 2025 19:48:03 +0530 Subject: [PATCH 1/9] fixed side issue collapsing --- index.html | 4 ++-- public/assets/css/default.css | 2 +- public/assets/js/main.js | 36 +++++++++++++++++++++++++++++++ src/components/Layout/Sidebar.jsx | 22 +++++++++++++------ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 24a9ef36..525634c5 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - - + diff --git a/public/assets/css/default.css b/public/assets/css/default.css index 8503dc87..6e2cafb7 100644 --- a/public/assets/css/default.css +++ b/public/assets/css/default.css @@ -31,7 +31,7 @@ } .app-brand-text { - font-size: 1.75rem; + font-size: 1rem; letter-spacing: -0.5px; /* text-transform: lowercase; */ } diff --git a/public/assets/js/main.js b/public/assets/js/main.js index cc356fa7..272f0369 100644 --- a/public/assets/js/main.js +++ b/public/assets/js/main.js @@ -148,5 +148,41 @@ function Main () { wheelPropagation: false }); } + }; +document.addEventListener("DOMContentLoaded", function () { + const html = document.documentElement; + + /****************************** + * SIDEBAR HOVER BEHAVIOR + ******************************/ + document.addEventListener("mouseover", function (e) { + const isInsideSidebar = e.target.closest("#layout-menu"); + + if (isInsideSidebar && html.classList.contains("layout-menu-collapsed")) { + html.classList.add("layout-menu-hover"); + } + }); + + document.addEventListener("mouseout", function (e) { + const leftSidebar = !e.relatedTarget || !e.relatedTarget.closest("#layout-menu"); + + if (leftSidebar) { + html.classList.remove("layout-menu-hover"); + } + }); + + /****************************** + * TOGGLE MENU BUTTON OVERRIDE + ******************************/ + document.body.addEventListener("click", function (e) { + const btn = e.target.closest(".layout-menu-toggle"); + if (!btn) return; + + e.preventDefault(); + + html.classList.toggle("layout-menu-collapsed"); + html.classList.remove("layout-menu-hover"); + }); +}); diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index 1f4b63b8..4d51ddf2 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -27,18 +27,26 @@ const Sidebar = () => { - - + + OnFieldWork logo + + + + OnField + Work + .com - OnField - Work - .com - + From b8df8a2bde30cc53d04e6c217e1ba2f62c4a342f Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Tue, 25 Nov 2025 20:01:11 +0530 Subject: [PATCH 2/9] initially setup purchaseform --- src/components/purchase/ManagePurchase.jsx | 117 ++++++++++++++++ .../purchase/PurchasePartyDetails.jsx | 31 +++++ .../purchase/PurchasePaymentDetails.jsx | 11 ++ src/components/purchase/PurchaseSchema.jsx | 127 ++++++++++++++++++ .../purchase/PurchaseTansportDetails.jsx | 11 ++ src/hooks/appHooks/useAppForm.js | 4 +- src/pages/purchase/PurchasePage.jsx | 57 ++++++++ src/router/AppRoutes.jsx | 7 + 8 files changed, 363 insertions(+), 2 deletions(-) create mode 100644 src/components/purchase/ManagePurchase.jsx create mode 100644 src/components/purchase/PurchasePartyDetails.jsx create mode 100644 src/components/purchase/PurchasePaymentDetails.jsx create mode 100644 src/components/purchase/PurchaseSchema.jsx create mode 100644 src/components/purchase/PurchaseTansportDetails.jsx create mode 100644 src/pages/purchase/PurchasePage.jsx diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx new file mode 100644 index 00000000..94db2836 --- /dev/null +++ b/src/components/purchase/ManagePurchase.jsx @@ -0,0 +1,117 @@ +import React, { useState } from "react"; +import { AppFormProvider, useAppForm } from "../../hooks/appHooks/useAppForm"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + defaultPurchaseValue, + PurchaseSchema, + getStepFields, +} from "./PurchaseSchema"; +import { defaultJobValue } from "../ServiceProject/ServiceProjectSchema"; +import PurchasePartyDetails from "./PurchasePartyDetails"; + +const ManagePurchase = () => { + const [activeTab, setActiveTab] = useState(0); + const [completedTabs, setCompletedTabs] = useState([]); + + const newTenantConfig = [ + { + name: "Contact Info", + icon: "bx bx-user bx-md", + subtitle: "Provide Contact Details", + component: , + }, + { + name: "Organization", + icon: "bx bx-buildings bx-md", + subtitle: "Organization Details", + component:
Invoice & Transport Details
, + }, + { + name: "SubScription", + icon: "bx bx-star bx-md", + component:
Payment & Financials
, + }, + ]; + const purchaseOrder = useAppForm({ + resolver: zodResolver(PurchaseSchema), + defaultJobValue: defaultPurchaseValue, + }); + const getCurrentTrigger = () => + activeTab === 2 ? subscriptionForm.trigger : tenantForm.trigger; + + const handleNext = async () => { + const currentStepFields = getStepFields(activeTab); + const trigger = getCurrentTrigger(); + const valid = await trigger(currentStepFields); + + if (valid) { + setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); + setActiveTab((prev) => Math.min(prev + 1, newTenantConfig.length - 1)); + } + }; + + const handlePrev = () => { + setActiveTab((prev) => Math.max(prev - 1, 0)); + }; + + const onsubmit = (formData) => {}; + return ( +
+ {/* New Parchase */} +
+ {newTenantConfig + .filter((step) => step.name.toLowerCase() !== "congratulation") + .map((step, index) => { + const isActive = activeTab === index; + const isCompleted = completedTabs.includes(index); + + return ( + +
+ +
+ {index < newTenantConfig.length - 1 && ( +
+ )} +
+ ); + })} +
+
+ +
+ {newTenantConfig[activeTab].component} +
+
+
+
+ ); +}; + +export default ManagePurchase; diff --git a/src/components/purchase/PurchasePartyDetails.jsx b/src/components/purchase/PurchasePartyDetails.jsx new file mode 100644 index 00000000..d7b63533 --- /dev/null +++ b/src/components/purchase/PurchasePartyDetails.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { useAppFormContext } from "../../hooks/appHooks/useAppForm"; + +const PurchasePartyDetails = ({ onNext }) => { + const { + register, + control, + trigger, + formState: { errors }, + } = useAppFormContext(); + + const handleNext = async () => { + const valid = await trigger([ + "title", + "projectId", + "organizationId", + "supplier", + "billingAddress", + "shippingAddress", + "purchaseOrderNumber", + "purchaseOrderDate", + "porformaInvoiceNo", + ]); + if (valid) { + onNext(); + } + }; + return
; +}; + +export default PurchasePartyDetails; diff --git a/src/components/purchase/PurchasePaymentDetails.jsx b/src/components/purchase/PurchasePaymentDetails.jsx new file mode 100644 index 00000000..fde938d4 --- /dev/null +++ b/src/components/purchase/PurchasePaymentDetails.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const PurchasePaymentDetails = () => { + return ( +
+ +
+ ) +} + +export default PurchasePaymentDetails diff --git a/src/components/purchase/PurchaseSchema.jsx b/src/components/purchase/PurchaseSchema.jsx new file mode 100644 index 00000000..04a5dc02 --- /dev/null +++ b/src/components/purchase/PurchaseSchema.jsx @@ -0,0 +1,127 @@ +import { z } from "zod"; + +export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => { + const allowedTypes = normalizeAllowedContentTypes(allowedContentType); + + return z.object({ + fileName: z.string().min(1, { message: "File name is required" }), + base64Data: z.string().min(1, { message: "File data is required" }), + contentType: z + .string() + .min(1, { message: "MIME type is required" }) + .refine( + (val) => (allowedTypes.length ? allowedTypes.includes(val) : true), + { + message: `File type must be one of: ${allowedTypes.join(", ")}`, + } + ), + fileSize: z + .number() + .int() + .nonnegative("fileSize must be ≥ 0") + .max( + (maxSizeAllowedInMB ?? 25) * 1024 * 1024, + `fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB` + ), + description: z.string().optional().default(""), + isActive: z.boolean(), + }); +}; + +export const PurchaseSchema = z.object({ + title: z.string().min(1, { message: "Title is required" }), + projectId: z.string().min(1, { message: "Project is required" }), + organizationId: z.string().min(1, { message: "Organization is required" }), + billingAddress: z.string().min(1, { message: "Address is required" }), + shippingAddress: z.string().min(1, { message: "Address is required" }), + purchaseOrderNumber: z.string().nullable(), + purchaseOrderDate: z.string().nullable(), + supplier: z.string().min(1, { message: "Supplier is required" }), + porformaInvoiceNo: z.string().nullable(), + // Supplier Details + + invoiceNo: z.string().min(1, { message: "Invoice No is required" }), + invoiceDate: z.string().min(1, { message: "Date is required" }), + ewayBillNo: z.string().min(1, { message: "E-Way Bill No is required" }), + ewayBillDate: z.string().min(1, { message: "E-Way Bill Date is required" }), + irnNo: z.string().min(1, { message: "IRN is required" }), + ackDate: z.string().min(1, { message: "Date is required" }), + ackNo: z.string().min(1, { message: "acknowledgement No is required" }), + + // Payment Detail + baseAmount: z.string().min(1, { message: "Base amount is required" }), + taxAmount: z.string().min(1, { message: "Tax amount is required" }), + totalAmount: z.string().min(1, { message: "Total amount is required" }), + paymentDueDate: z.string().nullable(), + TransportCharges: z.string().nullable(), + description: z.string().min(1, { message: "description is required" }), +}); + +// deliveryChallanNo: z +// .string() +// .min(1, { message: "Delivery Challan No is required" }), +// deliveryDate: z.string().min(1, { message: "Delevery Date is required" }), +// shippingAddress: z.string().min(1, { message: "Delevery Date is required" }), + +export const defaultPurchaseValue = { + title: null, + projectId: null, + organizationId: null, + billingAddress: null, + shippingAddress: null, + purchaseOrderNumber: null, + purchaseOrderDate: null, + supplier: null, + porformaInvoiceNo: null, + + invoiceNo: null, + invoiceDate: null, + ewayBillNo: null, + ewayBillDate: null, + irnNo: null, + ackDate: null, + ackNo: null, + + baseAmount: null, + taxAmount: null, + totalAmount: null, + paymentDueDate: null, + TransportCharges: null, + description: null, +}; + +export const getStepFields = (stepIndex) => { + const stepFieldMap = { + 0: [ + "title", + "projectId", + "organizationId", + "supplier", + "billingAddress", + "shippingAddress", + "purchaseOrderNumber", + "purchaseOrderDate", + "porformaInvoiceNo", + ], + 1: [ + "invoiceNo", + "invoiceDate", + "ewayBillNo", + "ewayBillDate", + "irnNo", + "ackNo", + "taxId", + "ackDate", + ], + 2: [ + "baseAmount", + "taxAmount", + "totalAmount", + "TransportCharges", + "paymentDueDate", + "description", + ], + }; + + return stepFieldMap[stepIndex] || []; +}; diff --git a/src/components/purchase/PurchaseTansportDetails.jsx b/src/components/purchase/PurchaseTansportDetails.jsx new file mode 100644 index 00000000..483845e6 --- /dev/null +++ b/src/components/purchase/PurchaseTansportDetails.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const PurchaseTansportDetails = () => { + return ( +
+ +
+ ) +} + +export default PurchaseTansportDetails diff --git a/src/hooks/appHooks/useAppForm.js b/src/hooks/appHooks/useAppForm.js index ea751205..f4ccbac0 100644 --- a/src/hooks/appHooks/useAppForm.js +++ b/src/hooks/appHooks/useAppForm.js @@ -1,6 +1,6 @@ -import { useForm, Controller,FormProvider } from "react-hook-form"; +import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form"; export const useAppForm = (config) => useForm(config); export const AppFormProvider = FormProvider; export const AppFormController = Controller; - +export const useAppFormContext = useFormContext; diff --git a/src/pages/purchase/PurchasePage.jsx b/src/pages/purchase/PurchasePage.jsx new file mode 100644 index 00000000..706c4a9b --- /dev/null +++ b/src/pages/purchase/PurchasePage.jsx @@ -0,0 +1,57 @@ +import React, { createContext, useContext, useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import showToast from "../../services/toastService"; +import GlobalModel from "../../components/common/GlobalModel"; +import ManagePurchase from "../../components/purchase/ManagePurchase"; + +export const PurchaseContext = createContext(); +export const usePurchaseContext = () => { + let context = useContext(PurchaseContext); + + if (!context) { + showToast("Please use Innne cntext", "warning"); + window.location = "/dashboard"; + } +}; +const PurchasePage = () => { + const [addePurchase, setAddedPurchase] = useState(false); + + const contextValue = {}; + return ( + +
+ +
+
+ + + +
+
+ + {/* MOdals */} + + setAddedPurchase(false)} + > + () => setAddedPurchase(false)} /> + +
+
+ ); +}; + +export default PurchasePage; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index dc556a6e..bee02723 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -62,6 +62,7 @@ import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; import ManageJob from "../components/ServiceProject/ServiceProjectJob/ManageJob"; import AdvancePaymentPage1 from "../pages/AdvancePayment/AdvancePaymentPage1"; +import PurchasePage from "../pages/purchase/PurchasePage"; const router = createBrowserRouter( [ { @@ -113,6 +114,8 @@ const router = createBrowserRouter( { path: "/activities/task", element: }, { path: "/activities/reports", element: }, { path: "/gallary", element: }, + + // Finance { path: "/expenses/:status?/:project?", element: }, { path: "/expenses", element: }, { path: "/payment-request", element: }, @@ -120,6 +123,10 @@ const router = createBrowserRouter( { path: "/advance-payment", element: }, { path: "/advance-payment/:employeeId", element: }, { path: "/collection", element: }, + + // Purchases and Inventory + { path: "/purchase-invoice", element: }, + // Administration { path: "/masters", element: }, { path: "/tenants", element: }, { path: "/tenants/new-tenant", element: }, From 82c1dc4b8e9fafb882114e730c156629e1cedc27 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 26 Nov 2025 12:29:40 +0530 Subject: [PATCH 3/9] created purchase form --- public/assets/css/core-extend.css | 12 + .../common/Forms/SelectFieldServerSide.jsx | 12 +- src/components/common/InputSuggestion.jsx | 1 - src/components/purchase/ManagePurchase.jsx | 138 ++++++---- .../purchase/PurchasePartyDetails.jsx | 241 ++++++++++++++++-- .../purchase/PurchasePaymentDetails.jsx | 148 ++++++++++- src/components/purchase/PurchaseSchema.jsx | 105 ++++---- .../purchase/PurchaseTansportDetails.jsx | 11 - .../purchase/PurchaseTransportDetails.jsx | 131 ++++++++++ 9 files changed, 659 insertions(+), 140 deletions(-) delete mode 100644 src/components/purchase/PurchaseTansportDetails.jsx create mode 100644 src/components/purchase/PurchaseTransportDetails.jsx diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index b05f71c1..127de638 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -11,6 +11,18 @@ top: var(--sticky-top, 0px) !important; z-index: 1025; } +.form-control-md { + min-height: calc(1.6em + 0.65rem + calc(var(--bs-border-width) * 2)); + padding: 0.18rem 0.60rem; + font-size: 0.875rem; /* ~14px */ + border-radius: var(--bs-border-radius); +} + +.form-control-md::file-selector-button { + padding: 0.32rem 0.75rem; + margin: -0.32rem -0.75rem; + margin-inline-end: 0.75rem; +} /* ===========================% Background_Colors %========================================================== */ diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index 47b1d5ad..d60be88b 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -200,7 +200,9 @@ export const SelectProjectField = ({ isFullObject = false, isMultiple = false, isAllProject = false, - disabled + disabled, + className, + errors }) => { const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); @@ -302,6 +304,10 @@ export const SelectProjectField = ({ {displayText} + + {errors?.projectId && ( +
{errors.projectId.message}
+ )} {/* DROPDOWN */} {open && ( @@ -377,6 +383,7 @@ export const SelectFieldSearch = ({ isMultiple = false, hookParams, useFetchHook, + errors=null, }) => { const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); @@ -512,6 +519,9 @@ export const SelectFieldSearch = ({ {displayText} + {errors && ( +
{errors.message}
+ )} {/* DROPDOWN */} {open && ( diff --git a/src/components/common/InputSuggestion.jsx b/src/components/common/InputSuggestion.jsx index 0a79fa35..b586abe1 100644 --- a/src/components/common/InputSuggestion.jsx +++ b/src/components/common/InputSuggestion.jsx @@ -50,7 +50,6 @@ const InputSuggestions = ({ {filteredList.map((org) => (
  • { const [activeTab, setActiveTab] = useState(0); const [completedTabs, setCompletedTabs] = useState([]); - const newTenantConfig = [ + const stepsConfig = [ { name: "Contact Info", icon: "bx bx-user bx-md", @@ -24,29 +25,29 @@ const ManagePurchase = () => { name: "Organization", icon: "bx bx-buildings bx-md", subtitle: "Organization Details", - component:
    Invoice & Transport Details
    , + component: , }, { - name: "SubScription", + name: "Subscription", icon: "bx bx-star bx-md", - component:
    Payment & Financials
    , + subtitle: "Payment & Financials", + component: , }, ]; + const purchaseOrder = useAppForm({ resolver: zodResolver(PurchaseSchema), - defaultJobValue: defaultPurchaseValue, + defaultValues: defaultPurchaseValue, + mode: "onChange", }); - const getCurrentTrigger = () => - activeTab === 2 ? subscriptionForm.trigger : tenantForm.trigger; const handleNext = async () => { const currentStepFields = getStepFields(activeTab); - const trigger = getCurrentTrigger(); - const valid = await trigger(currentStepFields); + const valid = await purchaseOrder.trigger(currentStepFields); if (valid) { setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); - setActiveTab((prev) => Math.min(prev + 1, newTenantConfig.length - 1)); + setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1)); } }; @@ -54,59 +55,84 @@ const ManagePurchase = () => { setActiveTab((prev) => Math.max(prev - 1, 0)); }; - const onsubmit = (formData) => {}; + const onSubmit = (formData) => { + console.log("PURCHASE DATA:", formData); + }; + return (
    - {/* New Parchase */} -
    - {newTenantConfig - .filter((step) => step.name.toLowerCase() !== "congratulation") - .map((step, index) => { - const isActive = activeTab === index; - const isCompleted = completedTabs.includes(index); + {/* Header */} +
    + {stepsConfig.map((step, index) => { + const isActive = activeTab === index; + const isCompleted = completedTabs.includes(index); - return ( - -
    - -
    - {index < newTenantConfig.length - 1 && ( -
    - )} -
    - ); - })} + + +
    + + {index < stepsConfig.length - 1 && ( +
    + )} + + ); + })}
    + + {/* Content */}
    - -
    - {newTenantConfig[activeTab].component} + + + {stepsConfig[activeTab].component} + +
    + + + {activeTab < stepsConfig.length - 1 ? ( + + ) : ( + + )} +
    diff --git a/src/components/purchase/PurchasePartyDetails.jsx b/src/components/purchase/PurchasePartyDetails.jsx index d7b63533..4870d38a 100644 --- a/src/components/purchase/PurchasePartyDetails.jsx +++ b/src/components/purchase/PurchasePartyDetails.jsx @@ -1,31 +1,232 @@ import React from "react"; -import { useAppFormContext } from "../../hooks/appHooks/useAppForm"; +import { AppFormController, useAppFormContext } from "../../hooks/appHooks/useAppForm"; +import Label from "../common/Label"; +import DatePicker from "../common/DatePicker"; +import { + SelectFieldSearch, + SelectProjectField, +} from "../common/Forms/SelectFieldServerSide"; +import { useOrganization, useOrganizationsList } from "../../hooks/useOrganization"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; -const PurchasePartyDetails = ({ onNext }) => { +const PurchasePartyDetails = () => { const { register, control, - trigger, + setValue, + watch, formState: { errors }, } = useAppFormContext(); - const handleNext = async () => { - const valid = await trigger([ - "title", - "projectId", - "organizationId", - "supplier", - "billingAddress", - "shippingAddress", - "purchaseOrderNumber", - "purchaseOrderDate", - "porformaInvoiceNo", - ]); - if (valid) { - onNext(); - } - }; - return
    ; + + return ( +
    + {/* Title */} +
    + + + + + {errors?.title && ( +
    {errors.title.message}
    + )} +
    + + {/* Project ID */} +
    + + setValue("projectId", val, { + shouldDirty: true, + shouldValidate: true, + }) + } + errors={errors} + /> +
    + + {/* Organization */} +
    + + {/* */} + ( + + )} + /> + +
    + + {/* Supplier */} +
    + + + + + {errors?.supplierId && ( +
    {errors.supplierId.message}
    + )} +
    + + {/* Billing Address */} +
    + + +