diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/contributors/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/contributors/page.tsx index f9bcf5a8..6b067421 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/contributors/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/contributors/page.tsx @@ -1,14 +1,14 @@ 'use client'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; import Image from 'next/image'; import { useParams } from 'next/navigation'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Button, Icon, Text, toast } from 'opub-ui'; -import { useEffect, useState } from 'react'; +import { GraphQL } from '@/lib/api'; import { Icons } from '@/components/icons'; import { Loading } from '@/components/loading'; -import { GraphQL } from '@/lib/api'; import { useEditStatus } from '../../context'; import CustomCombobox from './CustomCombobox'; import EntitySection from './EntitySelection'; @@ -25,10 +25,15 @@ import { } from './query'; const Details = () => { - const params = useParams<{ entityType: string; entitySlug: string; id: string }>(); + const params = useParams<{ + entityType: string; + entitySlug: string; + id: string; + }>(); const CONTRIBUTORS_ADD_SUCCESS_TOAST_ID = 'usecase-contributor-add-success'; const CONTRIBUTORS_ADD_ERROR_TOAST_ID = 'usecase-contributor-add-error'; - const CONTRIBUTORS_REMOVE_SUCCESS_TOAST_ID = 'usecase-contributor-remove-success'; + const CONTRIBUTORS_REMOVE_SUCCESS_TOAST_ID = + 'usecase-contributor-remove-success'; const CONTRIBUTORS_REMOVE_ERROR_TOAST_ID = 'usecase-contributor-remove-error'; const SUPPORTER_ADD_SUCCESS_TOAST_ID = 'usecase-supporter-add-success'; const SUPPORTER_ADD_ERROR_TOAST_ID = 'usecase-supporter-add-error'; @@ -69,13 +74,18 @@ const Details = () => { ); const Organizations: { data: any; isLoading: boolean; refetch: any } = - useQuery([`fetch_orgs`], () => GraphQL(OrgList, { - [params.entityType]: params.entitySlug, - }, [])); - + useQuery([`fetch_orgs`], () => + GraphQL( + OrgList, + { + [params.entityType]: params.entitySlug, + }, + [] + ) + ); const UseCaseData: { data: any; isLoading: boolean; refetch: any } = useQuery( - [`fetch_usecase_${params.id}`], + [`fetch_usecase`, params.id, params.entityType, params.entitySlug], () => GraphQL( FetchUsecaseInfo, @@ -119,18 +129,31 @@ const Details = () => { })); }, [UseCaseData?.data]); + const queryClient = useQueryClient(); + const { mutate: addContributor, isLoading: addContributorLoading } = useMutation( (input: { useCaseId: string; userId: string }) => - GraphQL(AddContributors, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + AddContributors, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { toast('Contributor added successfully', { id: CONTRIBUTORS_ADD_SUCCESS_TOAST_ID, }); - UseCaseData.refetch(); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -144,14 +167,26 @@ const Details = () => { const { mutate: removeContributor, isLoading: removeContributorLoading } = useMutation( (input: { useCaseId: string; userId: string }) => - GraphQL(RemoveContributor, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + RemoveContributor, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { toast('Contributor removed successfully', { id: CONTRIBUTORS_REMOVE_SUCCESS_TOAST_ID, }); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -164,15 +199,26 @@ const Details = () => { const { mutate: addSupporter, isLoading: addSupporterLoading } = useMutation( (input: { useCaseId: string; organizationId: string }) => - GraphQL(AddSupporters, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + AddSupporters, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { toast('Supporter added successfully', { id: SUPPORTER_ADD_SUCCESS_TOAST_ID, }); - UseCaseData.refetch(); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -186,14 +232,26 @@ const Details = () => { const { mutate: removeSupporter, isLoading: removeSupporterLoading } = useMutation( (input: { useCaseId: string; organizationId: string }) => - GraphQL(RemoveSupporters, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + RemoveSupporters, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { toast('Supporter removed successfully', { id: SUPPORTER_REMOVE_SUCCESS_TOAST_ID, }); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -206,13 +264,26 @@ const Details = () => { const { mutate: addPartner, isLoading: addPartnerLoading } = useMutation( (input: { useCaseId: string; organizationId: string }) => - GraphQL(AddPartners, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + AddPartners, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { - toast('Partner added successfully', { id: PARTNER_ADD_SUCCESS_TOAST_ID }); - UseCaseData.refetch(); + toast('Partner added successfully', { + id: PARTNER_ADD_SUCCESS_TOAST_ID, + }); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -226,14 +297,26 @@ const Details = () => { const { mutate: removePartner, isLoading: removePartnerLoading } = useMutation( (input: { useCaseId: string; organizationId: string }) => - GraphQL(RemovePartners, { - [params.entityType]: params.entitySlug, - }, input), + GraphQL( + RemovePartners, + { + [params.entityType]: params.entitySlug, + }, + input + ), { onSuccess: () => { toast('Partner removed successfully', { id: PARTNER_REMOVE_SUCCESS_TOAST_ID, }); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_usecase`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -353,7 +436,7 @@ const Details = () => { }} kind="tertiary" > -
+
{item.label}
diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/dashboards/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/dashboards/page.tsx index d733559c..26c89c03 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/dashboards/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/dashboards/page.tsx @@ -4,8 +4,8 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import { Button, Icon, Text, TextField, toast } from 'opub-ui'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Button, Icon, Spinner, Text, TextField, toast } from 'opub-ui'; import { GraphQL } from '@/lib/api'; import { Icons } from '@/components/icons'; @@ -75,7 +75,11 @@ const deleteDashboard: any = graphql(` `); const Dashboard = () => { - const params = useParams<{ entityType?: string; entitySlug?: string; id?: string }>(); + const params = useParams<{ + entityType?: string; + entitySlug?: string; + id?: string; + }>(); const DASHBOARD_ADD_SUCCESS_TOAST_ID = 'usecase-dashboard-add-success'; const DASHBOARD_SAVE_SUCCESS_TOAST_ID = 'usecase-dashboard-save-success'; const DASHBOARD_DELETE_SUCCESS_TOAST_ID = 'usecase-dashboard-delete-success'; @@ -94,9 +98,8 @@ const Dashboard = () => { typeof entitySlug === 'string' && typeof idParam === 'string'; - const usecaseId = isValidParams && idParam - ? Number.parseInt(idParam, 10) - : NaN; + const usecaseId = + isValidParams && idParam ? Number.parseInt(idParam, 10) : NaN; const isValidId = !Number.isNaN(usecaseId) && isValidParams; @@ -109,37 +112,46 @@ const Dashboard = () => { >([]); const [previousState, setPreviousState] = useState({}); - useQuery( - ['fetch_dashboardData', usecaseId], + const queryKey = [ + 'fetch_dashboardData', + params.entityType, + params.entitySlug, + usecaseId, + ]; + + const { data: dashboardData, isLoading }: { data: any; isLoading: boolean } = + useQuery( + queryKey, () => GraphQL(dashboardList, ownerArgs || {}, { usecaseId }), { refetchOnMount: true, refetchOnReconnect: true, enabled: isValidId, - onSuccess: (res: any) => { - setDashboards(res.usecaseDashboards || []); - setPreviousState( - Object.fromEntries( - res.usecaseDashboards.map((item: any) => [item.id, { ...item }]) - ) - ); - }, } ); + useEffect(() => { + if (dashboardData?.usecaseDashboards) { + setDashboards(dashboardData.usecaseDashboards); + setPreviousState( + Object.fromEntries( + dashboardData.usecaseDashboards.map((item: any) => [item.id, { ...item }]) + ) + ); + } + }, [dashboardData]); + + const queryClient = useQueryClient(); + const { mutate: addDashboard, isLoading: addLoading } = useMutation( ({ usecaseId }: { usecaseId: number }) => GraphQL(AddDashboard, ownerArgs || {}, { usecaseId }), { - onSuccess: (res: any) => { - const newDashboard = res.addUsecaseDashboard.data; - - setDashboards((prev: any) => [...prev, newDashboard]); - setPreviousState((prev: any) => ({ - ...prev, - [newDashboard.id]: { ...newDashboard }, - })); - toast.success('Dashboard added', { id: DASHBOARD_ADD_SUCCESS_TOAST_ID }); + onSuccess: () => { + queryClient.invalidateQueries({ queryKey }); + toast.success('Dashboard added', { + id: DASHBOARD_ADD_SUCCESS_TOAST_ID, + }); }, } ); @@ -148,6 +160,7 @@ const Dashboard = () => { GraphQL(updateDashboard, ownerArgs || {}, { id, name, link }), { onSuccess: ({ updateUsecaseDashboard }: any) => { + queryClient.invalidateQueries({ queryKey }); toast.success('Changes saved', { id: DASHBOARD_SAVE_SUCCESS_TOAST_ID }); setPreviousState((prev: any) => ({ ...prev, @@ -166,9 +179,11 @@ const Dashboard = () => { const { mutate: removeDashboard, isLoading: deleteLoading } = useMutation( (id: number) => GraphQL(deleteDashboard, ownerArgs || {}, { id }), { - onSuccess: (_, id) => { - setDashboards((prev) => prev.filter((d) => d.id !== id.toString())); - toast.success('Dashboard deleted', { id: DASHBOARD_DELETE_SUCCESS_TOAST_ID }); + onSuccess: () => { + toast.success('Dashboard deleted', { + id: DASHBOARD_DELETE_SUCCESS_TOAST_ID, + }); + queryClient.invalidateQueries({ queryKey }); }, onError: (error: any) => { toast( @@ -236,42 +251,50 @@ const Dashboard = () => { . - {dashboards?.map((item) => ( -
-
- handleChange(item.id, 'name', e)} - onBlur={() => handleSave(item)} - /> -
-
- handleChange(item.id, 'link', e)} - onBlur={() => handleSave(item)} - /> -
- + {isLoading ? ( +
+
- ))} - + ) : ( + <> + {dashboards?.map((item) => ( +
+
+ handleChange(item.id, 'name', e)} + onBlur={() => handleSave(item)} + /> +
+
+ handleChange(item.id, 'link', e)} + onBlur={() => handleSave(item)} + /> +
+ +
+ ))} + + + )}
); }; diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/details/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/details/page.tsx index 09b6ae83..04bbe418 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/details/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/details/page.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; import { UseCaseInputPartial } from '@/gql/generated/graphql'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { DropZone, Select, TextField, toast } from 'opub-ui'; // Assuming you are using these components @@ -76,7 +76,12 @@ const Details = () => { : fallback; const UseCaseData: { data: any; isLoading: boolean; refetch: any } = useQuery( - [`fetch_UseCaseData_details`], + [ + `fetch_UseCaseData_details`, + params.id, + params.entityType, + params.entitySlug, + ], () => GraphQL( FetchUseCase, @@ -156,6 +161,8 @@ const Details = () => { } }, [params.id, UsecasesData]); + const queryClient = useQueryClient(); + const { mutate, isLoading: editMutationLoading } = useMutation( (data: { data: UseCaseInputPartial }) => GraphQL( @@ -178,6 +185,14 @@ const Details = () => { ...prev, ...res.updateUseCase, })); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_UseCaseData_details`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx index 231a7eb4..b199af93 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useEffect, useState } from 'react'; +import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; import { MetadataModels, @@ -8,10 +10,8 @@ import { TypeTag, UpdateUseCaseMetadataInput, } from '@/gql/generated/graphql'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import { useParams } from 'next/navigation'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Combobox, Spinner, toast } from 'opub-ui'; -import { useEffect, useState } from 'react'; import { GraphQL } from '@/lib/api'; import { useEditStatus } from '../../context'; @@ -169,7 +169,12 @@ const Metadata = () => { const { setStatus } = useEditStatus(); const useCaseData: { data: any; isLoading: boolean } = useQuery( - [`fetch_UseCaseData_Metadata`], + [ + `fetch_UseCaseData_Metadata`, + params.id, + params.entityType, + params.entitySlug, + ], () => GraphQL( FetchUseCasedetails, @@ -247,7 +252,7 @@ const Metadata = () => { defaultVal['sdgs'] = data?.sdgs?.map((sdg: any) => { - const num = sdg.number + const num = sdg.number ? String(sdg.number).padStart(2, '0') : sdg.code.replace('SDG', '').padStart(2, '0'); return { @@ -272,14 +277,6 @@ const Metadata = () => { ); const [previousFormData, setPreviousFormData] = useState(formData); - useEffect(() => { - if (useCaseData.data?.useCases[0]) { - const updatedData = defaultValuesPrepFn(useCaseData.data.useCases[0]); - setFormData(updatedData); - setPreviousFormData(updatedData); - } - }, [useCaseData.data]); - const getSectorsList: { data: any; isLoading: boolean; error: any } = useQuery([`sectors_list_query`], () => GraphQL( @@ -302,8 +299,9 @@ const Metadata = () => { ) ); - const getSDGsList: { data: any; isLoading: boolean; error: any } = - useQuery([`sdgs_list_query`], () => + const getSDGsList: { data: any; isLoading: boolean; error: any } = useQuery( + [`sdgs_list_query`], + () => GraphQL( sdgsListQueryDoc, { @@ -311,7 +309,7 @@ const Metadata = () => { }, [] ) - ); + ); const getTagsList: { data: any; @@ -329,12 +327,18 @@ const Metadata = () => { ); const [isTagsListUpdated, setIsTagsListUpdated] = useState(false); + const queryClient = useQueryClient(); + // Update mutation const updateUseCase = useMutation( (data: { updateMetadataInput: UpdateUseCaseMetadataInput }) => - GraphQL(UpdateUseCaseMetadataMutation, { - [params.entityType]: params.entitySlug, - }, data), + GraphQL( + UpdateUseCaseMetadataMutation, + { + [params.entityType]: params.entitySlug, + }, + data + ), { onSuccess: (res: any) => { toast('Use case updated successfully', { @@ -347,6 +351,14 @@ const Metadata = () => { } setFormData(updatedData); setPreviousFormData(updatedData); + queryClient.invalidateQueries({ + queryKey: [ + `fetch_UseCaseData_Metadata`, + params.id, + params.entityType, + params.entitySlug, + ], + }); }, onError: (error: any) => { toast( @@ -361,6 +373,14 @@ const Metadata = () => { setStatus(updateUseCase.isLoading ? 'loading' : 'success'); // update based on mutation state }, [updateUseCase.isLoading]); + useEffect(() => { + if (useCaseData.data?.useCases?.[0]) { + const updatedData = defaultValuesPrepFn(useCaseData.data.useCases[0]); + setFormData(updatedData); + setPreviousFormData(updatedData); + } + }, [useCaseData.data]); + const handleChange = (field: string, value: any) => { setFormData((prevData) => ({ ...prevData, @@ -392,8 +412,9 @@ const Metadata = () => { ...Object.keys(transformedValues) .filter( (valueItem) => - !['sectors', 'tags', 'geographies', 'sdgs'].includes(valueItem) && - transformedValues[valueItem] !== '' + !['sectors', 'tags', 'geographies', 'sdgs'].includes( + valueItem + ) && transformedValues[valueItem] !== '' ) .map((key) => { return { @@ -441,7 +462,7 @@ const Metadata = () => { value: option, }))} label={metadataFormItem.label} - selectedValue={formData[metadataFormItem.id]} + selectedValue={formData[metadataFormItem.id] || ''} displaySelected onChange={(value) => { handleChange(metadataFormItem.id, value); @@ -464,7 +485,7 @@ const Metadata = () => { })) || []), ]} label={metadataFormItem.label + ' *'} - selectedValue={formData[metadataFormItem.id]} + selectedValue={formData[metadataFormItem.id] || []} displaySelected onChange={(value) => { handleChange(metadataFormItem.id, value); @@ -489,7 +510,7 @@ const Metadata = () => { requiredIndicator={true} list={ getSDGsList?.data?.sdgs?.map((item: any) => { - const num = item.number + const num = item.number ? String(item.number).padStart(2, '0') : item.code.replace('SDG', '').padStart(2, '0'); return { @@ -498,7 +519,7 @@ const Metadata = () => { }; }) || [] } - selectedValue={formData.sdgs} + selectedValue={formData.sdgs || []} onChange={(value) => { handleChange('sdgs', value); handleSave({ ...formData, sdgs: value }); @@ -518,7 +539,7 @@ const Metadata = () => { })) || [] } key={`tags-${getTagsList.data?.tags?.length}`} // forces remount on change - selectedValue={formData.tags} + selectedValue={formData.tags || []} onChange={(value) => { setIsTagsListUpdated(true); handleChange('tags', value); @@ -539,7 +560,7 @@ const Metadata = () => { value: item.id, })) || [] } - selectedValue={formData.sectors} + selectedValue={formData.sectors || []} onChange={(value) => { handleChange('sectors', value); handleSave({ ...formData, sectors: value }); @@ -556,7 +577,7 @@ const Metadata = () => { value: item.id, })) || [] } - selectedValue={formData.geographies} + selectedValue={formData.geographies || []} onChange={(value) => { handleChange('geographies', value); handleSave({ ...formData, geographies: value });