diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx index ed4b60b565..c6d2ea7874 100644 --- a/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx +++ b/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx @@ -79,6 +79,11 @@ export default function ManageProductModal({ const [cores, setCores] = useState(""); const [tps, setTps] = useState(""); const [description, setDescription] = useState(""); + const [addUpdateState, setAddUpdateState] = useState<{ + canAdd: boolean; + isSaving: boolean; + handleAdd: () => void; + } | null>(null); const patchProduct = usePatchDeploymentProduct(); const isSubmitting = patchProduct.isPending; @@ -308,7 +313,7 @@ export default function ManageProductModal({ } isLoading={false} onSaveUpdates={handleSaveUpdates} - onClose={handleClose} + onFormStateChange={setAddUpdateState} /> )} @@ -319,7 +324,7 @@ export default function ManageProductModal({ @@ -343,6 +348,28 @@ export default function ManageProductModal({ Save Changes ))} + {tabValue === 1 && addUpdateState && ( + addUpdateState.isSaving ? ( + + ) : ( + + ) + )} ); diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx index 91d09a21d7..5b819737f8 100644 --- a/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx +++ b/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx @@ -27,6 +27,7 @@ import { import { SquarePen, Trash2 } from "@wso2/oxygen-ui-icons-react"; import { useCallback, + useEffect, useMemo, useState, type ChangeEvent, @@ -42,7 +43,11 @@ export interface UpdateHistoryTabProps { productVersion: string; isLoading?: boolean; onSaveUpdates: (updates: ProductUpdate[]) => Promise; - onClose: () => void; + onFormStateChange?: (state: { + canAdd: boolean; + isSaving: boolean; + handleAdd: () => void; + }) => void; } interface UpdateFormData { @@ -69,7 +74,7 @@ export default function UpdateHistoryTab({ productVersion, isLoading, onSaveUpdates, - onClose, + onFormStateChange, }: UpdateHistoryTabProps): JSX.Element { const [form, setForm] = useState(INITIAL_FORM); const [editingIndex, setEditingIndex] = useState(null); @@ -184,6 +189,19 @@ export default function UpdateHistoryTab({ } }, [form, updates, onSaveUpdates]); + const isFormValid = !!form.updateLevel && !!form.date; + + // Notify parent of form state changes + useEffect(() => { + if (onFormStateChange) { + onFormStateChange({ + canAdd: isFormValid && !isSaving, + isSaving, + handleAdd: handleAddUpdate, + }); + } + }, [isFormValid, isSaving, handleAddUpdate, onFormStateChange]); + const handleEditClick = useCallback( (update: ProductUpdate) => { const index = updates.findIndex( @@ -425,32 +443,19 @@ export default function UpdateHistoryTab({ )} - - - - + {!onFormStateChange && ( + + + + )} ); @@ -489,6 +494,10 @@ function TimelineItem({ }: TimelineItemProps): JSX.Element { const [editForm, setEditForm] = useState(update); + useEffect(() => { + setEditForm(update); + }, [update, isEditing]); + const handleEditChange = (field: keyof ProductUpdate) => (e: ChangeEvent) => { const value = e.target.value; diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx index b0adbc2362..070a3826da 100644 --- a/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx +++ b/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx @@ -39,7 +39,6 @@ describe("UpdateHistoryTab", () => { productVersion: "2.1.0", isLoading: false, onSaveUpdates: vi.fn(), - onClose: vi.fn(), }; it("renders update history timeline", () => { diff --git a/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx b/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx index 5fe1d3988d..3c53df8d94 100644 --- a/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx +++ b/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx @@ -31,6 +31,7 @@ import ProjectDeployments from "@components/project-details/deployments/ProjectD import ProjectTimeTracking from "@components/project-details/time-tracking/ProjectTimeTracking"; import useGetProjectDetails from "@api/useGetProjectDetails"; import { useGetProjectStat } from "@api/useGetProjectStat"; +import useInfiniteProjects, { flattenProjectPages } from "@api/useGetProjects"; import { useLogger } from "@hooks/useLogger"; import { useLoader } from "@context/linear-loader/LoaderContext"; @@ -52,6 +53,14 @@ export default function ProjectDetails(): JSX.Element { sidebarCollapsed: boolean; }>() || { sidebarCollapsed: false }; + const { data: projectsData } = useInfiniteProjects({ enabled: true }); + const allProjects = flattenProjectPages(projectsData); + const currentProject = useMemo( + () => allProjects.find((p) => p.id === projectId), + [allProjects, projectId], + ); + const projectTypeLabel = currentProject?.type?.label; + const { data: project, isFetching: isProjectFetching, @@ -89,7 +98,6 @@ export default function ProjectDetails(): JSX.Element { } }, [projectError, statsError, logger]); - const projectTypeLabel = project?.type?.label; const hideDeploymentsAndTimeTracking = projectTypeLabel === PROJECT_TYPE_LABELS.CLOUD_SUPPORT || projectTypeLabel === PROJECT_TYPE_LABELS.CLOUD_EVALUATION_SUPPORT; @@ -185,7 +193,7 @@ export default function ProjectDetails(): JSX.Element { }; return ( - <> + {/* project page tabs */} {/* project page content */} - {renderContent()} - + {renderContent()} + ); }