refactor: only using runInAction for assignment

refactor: - using single types instead of lite version, - isLoading logic for label store
This commit is contained in:
Dakshesh Jain 2023-08-17 13:57:21 +05:30
parent 37df8684d7
commit 963eeb0a8c
13 changed files with 111 additions and 108 deletions

View File

@ -10,7 +10,7 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// helpers // helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types // types
import { IIssueFilterOptions, IState, IUserLite, TStateGroups, LabelLite } from "types"; import { IIssueFilterOptions, IState, IUserLite, TStateGroups, IIssueLabels } from "types";
// constants // constants
import { STATE_GROUP_COLORS } from "constants/state"; import { STATE_GROUP_COLORS } from "constants/state";
@ -18,7 +18,7 @@ type Props = {
filters: Partial<IIssueFilterOptions>; filters: Partial<IIssueFilterOptions>;
setFilters: (updatedFilter: Partial<IIssueFilterOptions>) => void; setFilters: (updatedFilter: Partial<IIssueFilterOptions>) => void;
clearAllFilters: (...args: any) => void; clearAllFilters: (...args: any) => void;
labels: LabelLite[] | undefined; labels: IIssueLabels[] | undefined;
members: IUserLite[] | undefined; members: IUserLite[] | undefined;
states: IState[] | undefined; states: IState[] | undefined;
}; };

View File

@ -18,7 +18,7 @@ import {
TagIcon, TagIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import type { LabelLite } from "types"; import type { IIssueLabels } from "types";
type Props = { type Props = {
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
@ -42,7 +42,7 @@ export const IssueLabelSelect: React.FC<Props> = observer(
if (workspaceSlug && projectId) loadLabels(workspaceSlug.toString(), projectId); if (workspaceSlug && projectId) loadLabels(workspaceSlug.toString(), projectId);
}, [workspaceSlug, projectId, loadLabels]); }, [workspaceSlug, projectId, loadLabels]);
const filteredOptions: LabelLite[] = labels?.filter((l) => const filteredOptions: IIssueLabels[] = labels?.filter((l) =>
l.name.toLowerCase().includes(query.toLowerCase()) l.name.toLowerCase().includes(query.toLowerCase())
); );

View File

@ -19,7 +19,7 @@ import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// icons // icons
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon } from "@heroicons/react/24/outline";
// types // types
import { IIssueLabels, LabelLite } from "types"; import { IIssueLabels } from "types";
// fetch-keys // fetch-keys
import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label"; import { getRandomLabelColor, LABEL_COLOR_OPTIONS } from "constants/label";
@ -27,7 +27,7 @@ type Props = {
labelForm: boolean; labelForm: boolean;
setLabelForm: React.Dispatch<React.SetStateAction<boolean>>; setLabelForm: React.Dispatch<React.SetStateAction<boolean>>;
isUpdating: boolean; isUpdating: boolean;
labelToUpdate: LabelLite | null; labelToUpdate: IIssueLabels | null;
onClose?: () => void; onClose?: () => void;
}; };

View File

@ -15,12 +15,12 @@ import useToast from "hooks/use-toast";
// ui // ui
import { DangerButton, SecondaryButton } from "components/ui"; import { DangerButton, SecondaryButton } from "components/ui";
// types // types
import type { ICurrentUserResponse, LabelLite } from "types"; import type { ICurrentUserResponse, IIssueLabels } from "types";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
data: LabelLite | null; data: IIssueLabels | null;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
}; };

View File

@ -11,12 +11,12 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
// icons // icons
import { RectangleStackIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { RectangleStackIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
// types // types
import { ICurrentUserResponse, LabelLite } from "types"; import { ICurrentUserResponse, IIssueLabels } from "types";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
handleClose: () => void; handleClose: () => void;
parent: LabelLite | undefined; parent: IIssueLabels | undefined;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
}; };
@ -37,7 +37,7 @@ export const LabelsListModal: React.FC<Props> = observer(
setQuery(""); setQuery("");
}; };
const addChildLabel = async (label: LabelLite) => { const addChildLabel = async (label: IIssueLabels) => {
if (!workspaceSlug || !projectId || !user) return; if (!workspaceSlug || !projectId || !user) return;
updateLabel( updateLabel(

View File

@ -20,14 +20,14 @@ import {
TrashIcon, TrashIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import { ICurrentUserResponse, LabelLite } from "types"; import { ICurrentUserResponse, IIssueLabels } from "types";
type Props = { type Props = {
label: LabelLite; label: IIssueLabels;
labelChildren: LabelLite[]; labelChildren: IIssueLabels[];
addLabelToGroup: (parentLabel: LabelLite) => void; addLabelToGroup: (parentLabel: IIssueLabels) => void;
editLabel: (label: LabelLite) => void; editLabel: (label: IIssueLabels) => void;
handleLabelDelete: (label: LabelLite) => void; handleLabelDelete: (label: IIssueLabels) => void;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
}; };
@ -39,7 +39,7 @@ export const SingleLabelGroup: React.FC<Props> = observer(
const { label: labelStore } = useMobxStore(); const { label: labelStore } = useMobxStore();
const { updateLabel } = labelStore; const { updateLabel } = labelStore;
const removeFromGroup = (label: LabelLite) => { const removeFromGroup = (label: IIssueLabels) => {
if (!workspaceSlug || !projectId || !user) return; if (!workspaceSlug || !projectId || !user) return;
updateLabel( updateLabel(

View File

@ -3,14 +3,14 @@ import React from "react";
// ui // ui
import { CustomMenu } from "components/ui"; import { CustomMenu } from "components/ui";
// types // types
import { LabelLite } from "types"; import { IIssueLabels } from "types";
//icons //icons
import { RectangleGroupIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; import { RectangleGroupIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
type Props = { type Props = {
label: LabelLite; label: IIssueLabels;
addLabelToGroup: (parentLabel: LabelLite) => void; addLabelToGroup: (parentLabel: IIssueLabels) => void;
editLabel: (label: LabelLite) => void; editLabel: (label: IIssueLabels) => void;
handleLabelDelete: () => void; handleLabelDelete: () => void;
}; };

View File

@ -19,32 +19,32 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
const items: BubbleMenuItem[] = [ const items: BubbleMenuItem[] = [
{ {
name: "bold", name: "bold",
isActive: () => props.editor.isActive("bold"), isActive: () => props?.editor?.isActive("bold")!,
command: () => props.editor.chain().focus().toggleBold().run(), command: () => props?.editor?.chain().focus().toggleBold().run()!,
icon: BoldIcon, icon: BoldIcon,
}, },
{ {
name: "italic", name: "italic",
isActive: () => props.editor.isActive("italic"), isActive: () => props?.editor?.isActive("italic")!,
command: () => props.editor.chain().focus().toggleItalic().run(), command: () => props?.editor?.chain().focus().toggleItalic().run()!,
icon: ItalicIcon, icon: ItalicIcon,
}, },
{ {
name: "underline", name: "underline",
isActive: () => props.editor.isActive("underline"), isActive: () => props?.editor?.isActive("underline")!,
command: () => props.editor.chain().focus().toggleUnderline().run(), command: () => props?.editor?.chain().focus().toggleUnderline().run()!,
icon: UnderlineIcon, icon: UnderlineIcon,
}, },
{ {
name: "strike", name: "strike",
isActive: () => props.editor.isActive("strike"), isActive: () => props?.editor?.isActive("strike")!,
command: () => props.editor.chain().focus().toggleStrike().run(), command: () => props?.editor?.chain().focus().toggleStrike().run()!,
icon: StrikethroughIcon, icon: StrikethroughIcon,
}, },
{ {
name: "code", name: "code",
isActive: () => props.editor.isActive("code"), isActive: () => props?.editor?.isActive("code")!,
command: () => props.editor.chain().focus().toggleCode().run(), command: () => props?.editor?.chain().focus().toggleCode().run()!,
icon: CodeIcon, icon: CodeIcon,
}, },
]; ];
@ -78,7 +78,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl" className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl"
> >
<NodeSelector <NodeSelector
editor={props.editor} editor={props?.editor!}
isOpen={isNodeSelectorOpen} isOpen={isNodeSelectorOpen}
setIsOpen={() => { setIsOpen={() => {
setIsNodeSelectorOpen(!isNodeSelectorOpen); setIsNodeSelectorOpen(!isNodeSelectorOpen);
@ -86,7 +86,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
}} }}
/> />
<LinkSelector <LinkSelector
editor={props.editor} editor={props?.editor!}
isOpen={isLinkSelectorOpen} isOpen={isLinkSelectorOpen}
setIsOpen={() => { setIsOpen={() => {
setIsLinkSelectorOpen(!isLinkSelectorOpen); setIsLinkSelectorOpen(!isLinkSelectorOpen);

View File

@ -31,7 +31,7 @@ import { PlusIcon } from "@heroicons/react/24/outline";
// images // images
import emptyLabel from "public/empty-state/label.svg"; import emptyLabel from "public/empty-state/label.svg";
// types // types
import { LabelLite } from "types"; import { IIssueLabels } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_DETAILS } from "constants/fetch-keys"; import { PROJECT_DETAILS } from "constants/fetch-keys";
@ -44,14 +44,14 @@ const LabelsSettings: NextPage = () => {
// edit label // edit label
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const [labelToUpdate, setLabelToUpdate] = useState<LabelLite | null>(null); const [labelToUpdate, setLabelToUpdate] = useState<IIssueLabels | null>(null);
// labels list modal // labels list modal
const [labelsListModal, setLabelsListModal] = useState(false); const [labelsListModal, setLabelsListModal] = useState(false);
const [parentLabel, setParentLabel] = useState<LabelLite | undefined>(undefined); const [parentLabel, setParentLabel] = useState<IIssueLabels | undefined>(undefined);
// delete label // delete label
const [selectDeleteLabel, setSelectDeleteLabel] = useState<LabelLite | null>(null); const [selectDeleteLabel, setSelectDeleteLabel] = useState<IIssueLabels | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
@ -79,12 +79,12 @@ const LabelsSettings: NextPage = () => {
setLabelForm(true); setLabelForm(true);
}; };
const addLabelToGroup = (parentLabel: LabelLite) => { const addLabelToGroup = (parentLabel: IIssueLabels) => {
setLabelsListModal(true); setLabelsListModal(true);
setParentLabel(parentLabel); setParentLabel(parentLabel);
}; };
const editLabel = (label: LabelLite) => { const editLabel = (label: IIssueLabels) => {
setLabelForm(true); setLabelForm(true);
setIsUpdating(true); setIsUpdating(true);
setLabelToUpdate(label); setLabelToUpdate(label);

View File

@ -5,10 +5,10 @@ import { action, observable, runInAction, makeAutoObservable } from "mobx";
import issueService from "services/issues.service"; import issueService from "services/issues.service";
// types // types
import type { IIssueLabels, LabelLite, ICurrentUserResponse, LabelForm } from "types"; import type { IIssueLabels, ICurrentUserResponse, LabelForm } from "types";
class LabelStore { class LabelStore {
labels: LabelLite[] = []; labels: IIssueLabels[] = [];
isLabelsLoading: boolean = false; isLabelsLoading: boolean = false;
rootStore: any | null = null; rootStore: any | null = null;
@ -30,24 +30,29 @@ class LabelStore {
*/ */
loadLabels = async (workspaceSlug: string, projectId: string) => { loadLabels = async (workspaceSlug: string, projectId: string) => {
this.isLabelsLoading = true; this.isLabelsLoading = this.labels.length === 0;
try { try {
const labelsResponse: IIssueLabels[] = await issueService.getIssueLabels( const labelsResponse: IIssueLabels[] = await issueService.getIssueLabels(
workspaceSlug, workspaceSlug,
projectId projectId
); );
runInAction(() => {
this.labels = labelsResponse.map((label) => ({ const _labels = [...(labelsResponse || [])].map((label) => ({
id: label.id, id: label.id,
name: label.name, name: label.name,
description: label.description, description: label.description,
color: label.color, color: label.color,
parent: label.parent, parent: label.parent,
})); }));
runInAction(() => {
this.labels = _labels;
this.isLabelsLoading = false; this.isLabelsLoading = false;
}); });
} catch (error) { } catch (error) {
runInAction(() => {
this.isLabelsLoading = false; this.isLabelsLoading = false;
});
console.error("Fetching labels error", error); console.error("Fetching labels error", error);
} }
}; };
@ -59,11 +64,11 @@ class LabelStore {
/** /**
* For provided query, this function returns all labels that contain query in their name from the labels store. * For provided query, this function returns all labels that contain query in their name from the labels store.
* @param query - query string * @param query - query string
* @returns {LabelLite[]} array of labels that contain query in their name * @returns {IIssueLabels[]} array of labels that contain query in their name
* @example * @example
* getFilteredLabels("labe") // [{ id: "1", name: "label1", description: "", color: "", parent: null }] * getFilteredLabels("labe") // [{ id: "1", name: "label1", description: "", color: "", parent: null }]
*/ */
getFilteredLabels = (query: string): LabelLite[] => getFilteredLabels = (query: string): IIssueLabels[] =>
this.labels.filter((label) => label.name.includes(query)); this.labels.filter((label) => label.name.includes(query));
createLabel = async ( createLabel = async (
@ -80,8 +85,7 @@ class LabelStore {
user user
); );
runInAction(() => { const _label = [
this.labels = [
...this.labels, ...this.labels,
{ {
id: labelResponse.id, id: labelResponse.id,
@ -90,8 +94,10 @@ class LabelStore {
color: labelResponse.color, color: labelResponse.color,
parent: labelResponse.parent, parent: labelResponse.parent,
}, },
]; ].sort((a, b) => a.name.localeCompare(b.name));
this.labels.sort((a, b) => a.name.localeCompare(b.name));
runInAction(() => {
this.labels = _label;
}); });
return labelResponse; return labelResponse;
} catch (error) { } catch (error) {
@ -116,7 +122,8 @@ class LabelStore {
user user
); );
const _labels = [...this.labels].map((label) => { const _labels = [...this.labels]
.map((label) => {
if (label.id === labelId) { if (label.id === labelId) {
return { return {
id: labelResponse.id, id: labelResponse.id,
@ -127,7 +134,8 @@ class LabelStore {
}; };
} }
return label; return label;
}); })
.sort((a, b) => a.name.localeCompare(b.name));
runInAction(() => { runInAction(() => {
this.labels = _labels; this.labels = _labels;
@ -146,8 +154,11 @@ class LabelStore {
) => { ) => {
try { try {
issueService.deleteIssueLabel(workspaceSlug, projectId, labelId, user); issueService.deleteIssueLabel(workspaceSlug, projectId, labelId, user);
const _labels = [...this.labels].filter((label) => label.id !== labelId);
runInAction(() => { runInAction(() => {
this.labels = this.labels.filter((label) => label.id !== labelId); this.labels = _labels;
}); });
} catch (error) { } catch (error) {
console.error("Deleting label error", error); console.error("Deleting label error", error);

View File

@ -18,7 +18,7 @@ export * from "./calendar";
export * from "./notifications"; export * from "./notifications";
export * from "./waitlist"; export * from "./waitlist";
export * from "./reaction"; export * from "./reaction";
export * from "./labels";
export type NestedKeyOf<ObjectType extends object> = { export type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object

View File

@ -148,36 +148,6 @@ export type IssuePriorities = {
user: string; user: string;
}; };
export interface IIssueLabels {
id: string;
created_at: Date;
updated_at: Date;
name: string;
description: string;
color: string;
created_by: string;
updated_by: string;
project: string;
project_detail: IProjectLite;
workspace: string;
workspace_detail: IWorkspaceLite;
parent: string | null;
}
export interface LabelForm {
name: string;
description: string;
color: string;
parent: string | null;
}
/**
* @description Issue label's lite version
*/
export interface LabelLite extends LabelForm {
id: string;
}
export interface IIssueActivity { export interface IIssueActivity {
actor: string; actor: string;
actor_detail: IUserLite; actor_detail: IUserLite;

22
apps/app/types/labels.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
export interface IIssueLabels {
id: string;
created_at?: Date;
updated_at?: Date;
name: string;
description: string;
color: string;
created_by?: string;
updated_by?: string;
project?: string;
project_detail?: IProjectLite;
workspace?: string;
workspace_detail?: IWorkspaceLite;
parent: string | null;
}
export interface LabelForm {
name: string;
description: string;
color: string;
parent: string | null;
}