diff --git a/web/components/estimates/create-update-estimate-modal.tsx b/web/components/estimates/create-update-estimate-modal.tsx index b3116b767..e2845936e 100644 --- a/web/components/estimates/create-update-estimate-modal.tsx +++ b/web/components/estimates/create-update-estimate-modal.tsx @@ -42,7 +42,9 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { const { workspaceSlug, projectId } = router.query; // store - const { projectEstimates: projectEstimatesStore } = useMobxStore(); + const { + projectEstimates: { createEstimate, updateEstimate }, + } = useMobxStore(); const { formState: { errors, isSubmitting }, @@ -60,11 +62,10 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { const { setToastAlert } = useToast(); - const createEstimate = async (payload: IEstimateFormData) => { + const handleCreateEstimate = async (payload: IEstimateFormData) => { if (!workspaceSlug || !projectId) return; - await projectEstimatesStore - .createEstimate(workspaceSlug.toString(), projectId.toString(), payload) + await createEstimate(workspaceSlug.toString(), projectId.toString(), payload) .then(() => { onClose(); }) @@ -83,13 +84,12 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { }); }; - const updateEstimate = async (payload: IEstimateFormData) => { + const handleUpdateEstimate = async (payload: IEstimateFormData) => { if (!workspaceSlug || !projectId || !data) return; - await projectEstimatesStore - .updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload) + await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload) .then(() => { - handleClose(); + onClose(); }) .catch((err) => { const error = err?.error; @@ -101,8 +101,6 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { message: errorString ?? "Estimate could not be updated. Please try again.", }); }); - - onClose(); }; const onSubmit = async (formData: FormValues) => { @@ -171,8 +169,8 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { else payload.estimate_points.push({ ...point }); } - if (data) await updateEstimate(payload); - else await createEstimate(payload); + if (data) await handleUpdateEstimate(payload); + else await handleCreateEstimate(payload); }; useEffect(() => { diff --git a/web/components/estimates/estimate-list-item.tsx b/web/components/estimates/estimate-list-item.tsx index beaa942d3..21f4f41f6 100644 --- a/web/components/estimates/estimate-list-item.tsx +++ b/web/components/estimates/estimate-list-item.tsx @@ -28,28 +28,27 @@ export const EstimateListItem: React.FC = observer((props) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; // store - const { project: projectStore } = useMobxStore(); - const { currentProjectDetails } = projectStore; + const { + project: { currentProjectDetails, updateProject }, + } = useMobxStore(); // hooks const { setToastAlert } = useToast(); const handleUseEstimate = async () => { if (!workspaceSlug || !projectId) return; - await projectStore - .updateProject(workspaceSlug.toString(), projectId.toString(), { - estimate: estimate.id, - }) - .catch((err) => { - const error = err?.error; - const errorString = Array.isArray(error) ? error[0] : error; + await updateProject(workspaceSlug.toString(), projectId.toString(), { + estimate: estimate.id, + }).catch((err) => { + const error = err?.error; + const errorString = Array.isArray(error) ? error[0] : error; - setToastAlert({ - type: "error", - title: "Error!", - message: errorString ?? "Estimate points could not be used. Please try again.", - }); + setToastAlert({ + type: "error", + title: "Error!", + message: errorString ?? "Estimate points could not be used. Please try again.", }); + }); }; return ( @@ -69,7 +68,7 @@ export const EstimateListItem: React.FC = observer((props) => {
{currentProjectDetails?.estimate !== estimate?.id && estimate?.points?.length > 0 && ( - )} diff --git a/web/components/estimates/estimate-list.tsx b/web/components/estimates/estimates-list.tsx similarity index 90% rename from web/components/estimates/estimate-list.tsx rename to web/components/estimates/estimates-list.tsx index 07770b183..df1088d7d 100644 --- a/web/components/estimates/estimate-list.tsx +++ b/web/components/estimates/estimates-list.tsx @@ -23,8 +23,10 @@ export const EstimatesList: React.FC = observer(() => { const { workspaceSlug, projectId } = router.query; // store - const { project: projectStore } = useMobxStore(); - const { currentProjectDetails } = projectStore; + const { + project: { currentProjectDetails, updateProject }, + projectEstimates: { projectEstimates, getProjectEstimateById }, + } = useMobxStore(); // states const [estimateFormOpen, setEstimateFormOpen] = useState(false); const [estimateToDelete, setEstimateToDelete] = useState(null); @@ -32,7 +34,7 @@ export const EstimatesList: React.FC = observer(() => { // hooks const { setToastAlert } = useToast(); // derived values - const estimatesList = projectStore.projectEstimates; + const estimatesList = projectEstimates; const editEstimate = (estimate: IEstimate) => { setEstimateFormOpen(true); @@ -42,7 +44,7 @@ export const EstimatesList: React.FC = observer(() => { const disableEstimates = () => { if (!workspaceSlug || !projectId) return; - projectStore.updateProject(workspaceSlug.toString(), projectId.toString(), { estimate: null }).catch((err) => { + updateProject(workspaceSlug.toString(), projectId.toString(), { estimate: null }).catch((err) => { const error = err?.error; const errorString = Array.isArray(error) ? error[0] : error; @@ -68,7 +70,7 @@ export const EstimatesList: React.FC = observer(() => { setEstimateToDelete(null)} - data={projectStore.getProjectEstimateById(estimateToDelete!)} + data={getProjectEstimateById(estimateToDelete!)} />
@@ -81,11 +83,12 @@ export const EstimatesList: React.FC = observer(() => { setEstimateFormOpen(true); setEstimateToUpdate(undefined); }} + size="sm" > Add Estimate {currentProjectDetails?.estimate && ( - )} diff --git a/web/components/estimates/index.ts b/web/components/estimates/index.ts index e9a22a53d..a0dea2d25 100644 --- a/web/components/estimates/index.ts +++ b/web/components/estimates/index.ts @@ -1,4 +1,5 @@ export * from "./create-update-estimate-modal"; export * from "./delete-estimate-modal"; -export * from "./estimate-select"; export * from "./estimate-list-item"; +export * from "./estimate-select"; +export * from "./estimates-list"; diff --git a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx index 0d1fce245..d991049ac 100644 --- a/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/archived-issue-root.tsx @@ -22,6 +22,7 @@ export const ArchivedIssueListLayout: FC = observer(() => { projectLabel: { projectLabels }, projectMember: { projectMembers }, projectState: projectStateStore, + projectEstimates: { projectEstimates }, archivedIssues: archivedIssueStore, archivedIssueFilters: archivedIssueFiltersStore, } = useMobxStore(); @@ -48,9 +49,7 @@ export const ArchivedIssueListLayout: FC = observer(() => { const stateGroups = ISSUE_STATE_GROUPS || null; const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; const estimates = - projectDetails?.estimate !== null - ? projectStore.projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null - : null; + projectDetails?.estimate !== null ? projectEstimates?.find((e) => e.id === projectDetails?.estimate) || null : null; return (
diff --git a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx index b92a57fa8..c47b3ceb8 100644 --- a/web/components/issues/issue-layouts/list/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/cycle-root.tsx @@ -24,6 +24,7 @@ export const CycleListLayout: React.FC = observer(() => { projectLabel: { projectLabels }, projectMember: { projectMembers }, projectState: projectStateStore, + projectEstimates: { projectEstimates }, issueFilter: issueFilterStore, cycleIssue: cycleIssueStore, issueDetail: issueDetailStore, @@ -64,7 +65,7 @@ export const CycleListLayout: React.FC = observer(() => { const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; const estimates = currentProjectDetails?.estimate !== null - ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null + ? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null : null; return ( diff --git a/web/components/issues/issue-layouts/list/roots/module-root.tsx b/web/components/issues/issue-layouts/list/roots/module-root.tsx index 7fa1f4718..e27379df2 100644 --- a/web/components/issues/issue-layouts/list/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/module-root.tsx @@ -24,6 +24,7 @@ export const ModuleListLayout: React.FC = observer(() => { projectLabel: { projectLabels }, projectMember: { projectMembers }, projectState: projectStateStore, + projectEstimates: { projectEstimates }, issueFilter: issueFilterStore, moduleIssue: moduleIssueStore, issueDetail: issueDetailStore, @@ -64,7 +65,7 @@ export const ModuleListLayout: React.FC = observer(() => { const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; const estimates = currentProjectDetails?.estimate !== null - ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null + ? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null : null; return ( diff --git a/web/components/issues/issue-layouts/list/roots/project-root.tsx b/web/components/issues/issue-layouts/list/roots/project-root.tsx index cc78145f0..91a04f57d 100644 --- a/web/components/issues/issue-layouts/list/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/project-root.tsx @@ -23,6 +23,7 @@ export const ListLayout: FC = observer(() => { projectLabel: { projectLabels }, projectMember: { projectMembers }, projectState: projectStateStore, + projectEstimates: { projectEstimates }, issue: issueStore, issueDetail: issueDetailStore, issueFilter: issueFilterStore, @@ -54,7 +55,7 @@ export const ListLayout: FC = observer(() => { const projects = workspaceSlug ? projectStore?.projects[workspaceSlug.toString()] || null : null; const estimates = currentProjectDetails?.estimate !== null - ? projectStore.projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null + ? projectEstimates?.find((e) => e.id === currentProjectDetails?.estimate) || null : null; return ( diff --git a/web/components/issues/issue-layouts/properties/estimates.tsx b/web/components/issues/issue-layouts/properties/estimates.tsx index 432a39f4d..3c7179f2e 100644 --- a/web/components/issues/issue-layouts/properties/estimates.tsx +++ b/web/components/issues/issue-layouts/properties/estimates.tsx @@ -52,11 +52,14 @@ export const IssuePropertyEstimates: React.FC = observe ], }); - const { project: projectStore } = useMobxStore(); + const { + project: { project_details }, + projectEstimates: { projectEstimates }, + } = useMobxStore(); - const projectDetails = projectId ? projectStore.project_details[projectId] : null; + const projectDetails = projectId ? project_details[projectId] : null; const isEstimateEnabled = projectDetails?.estimate !== null; - const estimates = projectId ? projectStore.estimates?.[projectId] : null; + const estimates = projectEstimates; const estimatePoints = projectDetails && isEstimateEnabled ? estimates?.find((e) => e.id === projectDetails.estimate)?.points : null; diff --git a/web/layouts/auth-layout/project-wrapper.tsx b/web/layouts/auth-layout/project-wrapper.tsx index 8e7d27f19..845f86900 100644 --- a/web/layouts/auth-layout/project-wrapper.tsx +++ b/web/layouts/auth-layout/project-wrapper.tsx @@ -20,10 +20,11 @@ export const ProjectAuthWrapper: FC = observer((props) => { // store const { user: { fetchUserProjectInfo, projectMemberInfo, hasPermissionToProject }, - project: { fetchProjectDetails, fetchProjectEstimates, workspaceProjects }, + project: { fetchProjectDetails, workspaceProjects }, projectLabel: { fetchProjectLabels }, projectMember: { fetchProjectMembers }, projectState: { fetchProjectStates }, + projectEstimates: { fetchProjectEstimates }, cycle: { fetchCycles }, module: { fetchModules }, projectViews: { fetchAllViews }, diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 152bbc522..783243c4a 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -4,7 +4,7 @@ import { AppLayout } from "layouts/app-layout"; import { ProjectSettingLayout } from "layouts/settings-layout"; // components import { ProjectSettingHeader } from "components/headers"; -import { EstimatesList } from "components/estimates/estimate-list"; +import { EstimatesList } from "components/estimates"; // types import { NextPageWithLayout } from "types/app"; diff --git a/web/store/project/project-estimates.store.ts b/web/store/project/project-estimates.store.ts index f634d3fd9..b7e31af1f 100644 --- a/web/store/project/project-estimates.store.ts +++ b/web/store/project/project-estimates.store.ts @@ -1,4 +1,4 @@ -import { observable, action, makeObservable, runInAction } from "mobx"; +import { observable, action, makeObservable, runInAction, computed } from "mobx"; // types import { RootStore } from "../root"; import { IEstimate, IEstimateFormData } from "types"; @@ -9,7 +9,14 @@ export interface IProjectEstimateStore { loader: boolean; error: any | null; - // estimates + // observables + estimates: { + [projectId: string]: IEstimate[] | null; // project_id: members + } | null; + + // actions + getProjectEstimateById: (estimateId: string) => IEstimate | null; + fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise; createEstimate: (workspaceSlug: string, projectId: string, data: IEstimateFormData) => Promise; updateEstimate: ( workspaceSlug: string, @@ -18,14 +25,23 @@ export interface IProjectEstimateStore { data: IEstimateFormData ) => Promise; deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise; + + // computed + projectEstimates: IEstimate[] | undefined; } export class ProjectEstimatesStore implements IProjectEstimateStore { loader: boolean = false; error: any | null = null; + // observables + estimates: { + [projectId: string]: IEstimate[]; // projectId: estimates + } | null = {}; + // root store rootStore; + // service projectService; estimateService; @@ -36,10 +52,17 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { loader: observable, error: observable, - // estimates + estimates: observable.ref, + + // actions + getProjectEstimateById: action, + fetchProjectEstimates: action, createEstimate: action, updateEstimate: action, deleteEstimate: action, + + // computed + projectEstimates: computed, }); this.rootStore = _rootStore; @@ -47,6 +70,43 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { this.estimateService = new ProjectEstimateService(); } + get projectEstimates() { + const projectId = this.rootStore.project.projectId; + + if (!projectId) return undefined; + return this.estimates?.[projectId] || undefined; + } + + getProjectEstimateById = (estimateId: string) => { + const estimates = this.projectEstimates; + if (!estimates) return null; + const estimateInfo: IEstimate | null = estimates.find((estimate) => estimate.id === estimateId) || null; + return estimateInfo; + }; + + fetchProjectEstimates = async (workspaceSlug: string, projectId: string) => { + try { + this.loader = true; + this.error = null; + + const estimatesResponse = await this.estimateService.getEstimatesList(workspaceSlug, projectId); + const _estimates = { + ...this.estimates, + [projectId]: estimatesResponse, + }; + + runInAction(() => { + this.estimates = _estimates; + this.loader = false; + this.error = null; + }); + } catch (error) { + console.error(error); + this.loader = false; + this.error = error; + } + }; + createEstimate = async (workspaceSlug: string, projectId: string, data: IEstimateFormData) => { try { const response = await this.estimateService.createEstimate(workspaceSlug, projectId, data); @@ -57,9 +117,9 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { }; runInAction(() => { - this.rootStore.project.estimates = { - ...this.rootStore.project.estimates, - [projectId]: [responseEstimate, ...(this.rootStore.project.estimates?.[projectId] || [])], + this.estimates = { + ...this.estimates, + [projectId]: [responseEstimate, ...(this.estimates?.[projectId] || [])], }; }); @@ -71,12 +131,12 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { }; updateEstimate = async (workspaceSlug: string, projectId: string, estimateId: string, data: IEstimateFormData) => { - const originalEstimates = this.rootStore.project.getProjectEstimateById(estimateId); + const originalEstimates = this.getProjectEstimateById(estimateId); runInAction(() => { - this.rootStore.project.estimates = { - ...this.rootStore.project.estimates, - [projectId]: (this.rootStore.project.estimates?.[projectId] || [])?.map((estimate) => + this.estimates = { + ...this.estimates, + [projectId]: (this.estimates?.[projectId] || [])?.map((estimate) => estimate.id === estimateId ? { ...estimate, ...data.estimate } : estimate ), }; @@ -84,15 +144,15 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { try { const response = await this.estimateService.patchEstimate(workspaceSlug, projectId, estimateId, data); - await this.rootStore.project.fetchProjectEstimates(workspaceSlug, projectId); + await this.fetchProjectEstimates(workspaceSlug, projectId); return response; } catch (error) { console.log("Failed to update estimate from project store"); runInAction(() => { - this.rootStore.project.estimates = { - ...this.rootStore.project.estimates, - [projectId]: (this.rootStore.project.estimates?.[projectId] || [])?.map((estimate) => + this.estimates = { + ...this.estimates, + [projectId]: (this.estimates?.[projectId] || [])?.map((estimate) => estimate.id === estimateId ? { ...estimate, ...originalEstimates } : estimate ), }; @@ -102,14 +162,12 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { }; deleteEstimate = async (workspaceSlug: string, projectId: string, estimateId: string) => { - const originalEstimateList = this.rootStore.project.projectEstimates || []; + const originalEstimateList = this.projectEstimates || []; runInAction(() => { - this.rootStore.project.estimates = { - ...this.rootStore.project.estimates, - [projectId]: (this.rootStore.project.estimates?.[projectId] || [])?.filter( - (estimate) => estimate.id !== estimateId - ), + this.estimates = { + ...this.estimates, + [projectId]: (this.estimates?.[projectId] || [])?.filter((estimate) => estimate.id !== estimateId), }; }); @@ -120,8 +178,8 @@ export class ProjectEstimatesStore implements IProjectEstimateStore { console.log("Failed to delete estimate from project store"); // reverting back to original estimate list runInAction(() => { - this.rootStore.project.estimates = { - ...this.rootStore.project.estimates, + this.estimates = { + ...this.estimates, [projectId]: originalEstimateList, }; }); diff --git a/web/store/project/project.store.ts b/web/store/project/project.store.ts index 3216a5e74..1a4d0bcfe 100644 --- a/web/store/project/project.store.ts +++ b/web/store/project/project.store.ts @@ -1,9 +1,9 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx"; // types import { RootStore } from "../root"; -import { IProject, IEstimate } from "types"; +import { IProject } from "types"; // services -import { ProjectService, ProjectStateService, ProjectEstimateService } from "services/project"; +import { ProjectService, ProjectStateService } from "services/project"; import { IssueService, IssueLabelService } from "services/issue"; export interface IProjectStore { @@ -16,14 +16,10 @@ export interface IProjectStore { project_details: { [projectId: string]: IProject; // projectId: project Info }; - estimates: { - [projectId: string]: IEstimate[] | null; // project_id: members - } | null; // computed searchedProjects: IProject[]; workspaceProjects: IProject[] | null; - projectEstimates: IEstimate[] | null; joinedProjects: IProject[]; favoriteProjects: IProject[]; currentProjectDetails: IProject | undefined; @@ -34,10 +30,8 @@ export interface IProjectStore { getProjectById: (workspaceSlug: string, projectId: string) => IProject | null; - getProjectEstimateById: (estimateId: string) => IEstimate | null; fetchProjects: (workspaceSlug: string) => Promise; fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise; - fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise; addProjectToFavorites: (workspaceSlug: string, projectId: string) => Promise; removeProjectFromFavorites: (workspaceSlug: string, projectId: string) => Promise; @@ -62,9 +56,6 @@ export class ProjectStore implements IProjectStore { project_details: { [projectId: string]: IProject; // projectId: project } = {}; - estimates: { - [projectId: string]: IEstimate[]; // projectId: estimates - } | null = {}; // root store rootStore; @@ -73,7 +64,6 @@ export class ProjectStore implements IProjectStore { issueLabelService; issueService; stateService; - estimateService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -86,14 +76,10 @@ export class ProjectStore implements IProjectStore { projects: observable.ref, project_details: observable.ref, - estimates: observable.ref, - // computed searchedProjects: computed, workspaceProjects: computed, - projectEstimates: computed, - currentProjectDetails: computed, joinedProjects: computed, @@ -106,9 +92,6 @@ export class ProjectStore implements IProjectStore { fetchProjectDetails: action, getProjectById: action, - getProjectEstimateById: action, - - fetchProjectEstimates: action, addProjectToFavorites: action, removeProjectFromFavorites: action, @@ -125,7 +108,6 @@ export class ProjectStore implements IProjectStore { this.issueService = new IssueService(); this.issueLabelService = new IssueLabelService(); this.stateService = new ProjectStateService(); - this.estimateService = new ProjectEstimateService(); } get searchedProjects() { @@ -164,11 +146,6 @@ export class ProjectStore implements IProjectStore { return this.projects?.[this.rootStore.workspace.workspaceSlug]?.filter((p) => p.is_favorite); } - get projectEstimates() { - if (!this.projectId) return null; - return this.estimates?.[this.projectId] || null; - } - // actions setProjectId = (projectId: string | null) => { this.projectId = projectId; @@ -223,37 +200,6 @@ export class ProjectStore implements IProjectStore { return projectInfo; }; - getProjectEstimateById = (estimateId: string) => { - if (!this.projectId) return null; - const estimates = this.projectEstimates; - if (!estimates) return null; - const estimateInfo: IEstimate | null = estimates.find((estimate) => estimate.id === estimateId) || null; - return estimateInfo; - }; - - fetchProjectEstimates = async (workspaceSlug: string, projectId: string) => { - try { - this.loader = true; - this.error = null; - - const estimatesResponse = await this.estimateService.getEstimatesList(workspaceSlug, projectId); - const _estimates = { - ...this.estimates, - [projectId]: estimatesResponse, - }; - - runInAction(() => { - this.estimates = _estimates; - this.loader = false; - this.error = null; - }); - } catch (error) { - console.error(error); - this.loader = false; - this.error = error; - } - }; - addProjectToFavorites = async (workspaceSlug: string, projectId: string) => { try { runInAction(() => {