refactor: project estimates store (#2801)

* refactor: remove estimates from project store

* chore: update all the instances of the old store

* chore: update store declaration structure
This commit is contained in:
Aaryan Khandelwal 2023-11-20 15:58:40 +05:30 committed by sriram veeraghanta
parent d3c85b1336
commit af8804eb12
13 changed files with 134 additions and 123 deletions

View File

@ -42,7 +42,9 @@ export const CreateUpdateEstimateModal: React.FC<Props> = 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<Props> = 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<Props> = 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<Props> = 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<Props> = 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(() => {

View File

@ -28,28 +28,27 @@ export const EstimateListItem: React.FC<Props> = 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<Props> = observer((props) => {
</div>
<div className="flex items-center gap-2">
{currentProjectDetails?.estimate !== estimate?.id && estimate?.points?.length > 0 && (
<Button variant="neutral-primary" onClick={handleUseEstimate}>
<Button variant="neutral-primary" onClick={handleUseEstimate} size="sm">
Use
</Button>
)}

View File

@ -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<string | null>(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(() => {
<DeleteEstimateModal
isOpen={!!estimateToDelete}
handleClose={() => setEstimateToDelete(null)}
data={projectStore.getProjectEstimateById(estimateToDelete!)}
data={getProjectEstimateById(estimateToDelete!)}
/>
<section className="flex items-center justify-between py-3.5 border-b border-custom-border-100">
@ -81,11 +83,12 @@ export const EstimatesList: React.FC = observer(() => {
setEstimateFormOpen(true);
setEstimateToUpdate(undefined);
}}
size="sm"
>
Add Estimate
</Button>
{currentProjectDetails?.estimate && (
<Button variant="neutral-primary" onClick={disableEstimates}>
<Button variant="neutral-primary" onClick={disableEstimates} size="sm">
Disable Estimates
</Button>
)}

View File

@ -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";

View File

@ -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 (
<div className="relative w-full h-full bg-custom-background-90">

View File

@ -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 (

View File

@ -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 (

View File

@ -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 (

View File

@ -52,11 +52,14 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = 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;

View File

@ -20,10 +20,11 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = 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 },

View File

@ -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";

View File

@ -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<void>;
createEstimate: (workspaceSlug: string, projectId: string, data: IEstimateFormData) => Promise<IEstimate>;
updateEstimate: (
workspaceSlug: string,
@ -18,14 +25,23 @@ export interface IProjectEstimateStore {
data: IEstimateFormData
) => Promise<IEstimate>;
deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<void>;
// 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,
};
});

View File

@ -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<void>;
fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise<any>;
fetchProjectEstimates: (workspaceSlug: string, projectId: string) => Promise<any>;
addProjectToFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
removeProjectFromFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
@ -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(() => {