change module and cycle types

This commit is contained in:
rahulramesha 2024-02-19 18:00:07 +05:30
parent 199d50da0f
commit 701500df9f
21 changed files with 132 additions and 111 deletions

View File

@ -32,8 +32,7 @@ export interface ICycle {
name: string; name: string;
owned_by: string; owned_by: string;
progress_snapshot: TProgressSnapshot; progress_snapshot: TProgressSnapshot;
project: string; project_id: string;
project_detail: IProjectLite;
status: TCycleGroups; status: TCycleGroups;
sort_order: number; sort_order: number;
start_date: string | null; start_date: string | null;
@ -42,12 +41,11 @@ export interface ICycle {
unstarted_issues: number; unstarted_issues: number;
updated_at: Date; updated_at: Date;
updated_by: string; updated_by: string;
assignees: IUserLite[]; assignee_ids: string[];
view_props: { view_props: {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
}; };
workspace: string; workspace_id: string;
workspace_detail: IWorkspaceLite;
} }
export type TProgressSnapshot = { export type TProgressSnapshot = {

View File

@ -27,16 +27,12 @@ export interface IModule {
labels: TLabelsDistribution[]; labels: TLabelsDistribution[];
}; };
id: string; id: string;
lead: string | null; lead_id: string | null;
lead_detail: IUserLite | null;
link_module: ILinkDetails[]; link_module: ILinkDetails[];
links_list: ModuleLink[]; member_ids: string[];
members: string[];
members_detail: IUserLite[];
is_favorite: boolean; is_favorite: boolean;
name: string; name: string;
project: string; project_id: string;
project_detail: IProjectLite;
sort_order: number; sort_order: number;
start_date: string | null; start_date: string | null;
started_issues: number; started_issues: number;
@ -49,8 +45,7 @@ export interface IModule {
view_props: { view_props: {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
}; };
workspace: string; workspace_id: string;
workspace_detail: IWorkspaceLite;
} }
export interface ModuleIssueResponse { export interface ModuleIssueResponse {

View File

@ -21,6 +21,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined; const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined; const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined;
const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by) : undefined; const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by) : undefined;
const moduleLeadDetails = moduleDetails && moduleDetails.lead_id ? getUserDetails(moduleDetails.lead_id) : undefined;
return ( return (
<> <>
@ -57,7 +58,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Lead</h6> <h6 className="text-custom-text-200">Lead</h6>
<span>{moduleDetails.lead_detail?.display_name}</span> {moduleLeadDetails && <span>{moduleLeadDetails?.display_name}</span>}
</div> </div>
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<h6 className="text-custom-text-200">Start Date</h6> <h6 className="text-custom-text-200">Start Date</h6>

View File

@ -5,7 +5,7 @@ import { mutate } from "swr";
// services // services
import { AnalyticsService } from "services/analytics.service"; import { AnalyticsService } from "services/analytics.service";
// hooks // hooks
import { useCycle, useModule, useProject, useUser } from "hooks/store"; import { useCycle, useModule, useProject, useUser, useWorkspace } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics"; import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
@ -39,6 +39,8 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
// store hooks // store hooks
const { currentUser } = useUser(); const { currentUser } = useUser();
const { workspaceProjectIds, getProjectById } = useProject(); const { workspaceProjectIds, getProjectById } = useProject();
const { getWorkspaceById } = useWorkspace();
const { fetchCycleDetails, getCycleById } = useCycle(); const { fetchCycleDetails, getCycleById } = useCycle();
const { fetchModuleDetails, getModuleById } = useModule(); const { fetchModuleDetails, getModuleById } = useModule();
@ -70,11 +72,14 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
if (cycleDetails || moduleDetails) { if (cycleDetails || moduleDetails) {
const details = cycleDetails || moduleDetails; const details = cycleDetails || moduleDetails;
eventPayload.workspaceId = details?.workspace_detail?.id; const currentProjectDetails = getProjectById(details?.project_id || "");
eventPayload.workspaceName = details?.workspace_detail?.name; const currentWorkspaceDetails = getWorkspaceById(details?.workspace_id || "");
eventPayload.projectId = details?.project_detail.id;
eventPayload.projectIdentifier = details?.project_detail.identifier; eventPayload.workspaceId = details?.workspace_id;
eventPayload.projectName = details?.project_detail.name; eventPayload.workspaceName = currentWorkspaceDetails?.name;
eventPayload.projectId = details?.project_id;
eventPayload.projectIdentifier = currentProjectDetails?.identifier;
eventPayload.projectName = currentProjectDetails?.name;
} }
if (cycleDetails) { if (cycleDetails) {
@ -138,14 +143,18 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjectIds; const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjectIds;
return ( return (
<div className={cn("relative h-full flex w-full gap-2 justify-between items-start px-5 py-4 bg-custom-sidebar-background-100", !isProjectLevel ? "flex-col" : "")} <div
className={cn(
"relative h-full flex w-full gap-2 justify-between items-start px-5 py-4 bg-custom-sidebar-background-100",
!isProjectLevel ? "flex-col" : ""
)}
> >
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200"> <div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<LayersIcon height={14} width={14} /> <LayersIcon height={14} width={14} />
{analytics ? analytics.total : "..."} <div className={cn(isProjectLevel ? "hidden md:block" : "")}>Issues</div> {analytics ? analytics.total : "..."}
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>Issues</div>
</div> </div>
{isProjectLevel && ( {isProjectLevel && (
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200"> <div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
@ -154,8 +163,8 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
(cycleId (cycleId
? cycleDetails?.created_at ? cycleDetails?.created_at
: moduleId : moduleId
? moduleDetails?.created_at ? moduleDetails?.created_at
: projectDetails?.created_at) ?? "" : projectDetails?.created_at) ?? ""
)} )}
</div> </div>
)} )}

View File

@ -222,12 +222,13 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<span className="text-custom-text-200">{cycleOwnerDetails?.display_name}</span> <span className="text-custom-text-200">{cycleOwnerDetails?.display_name}</span>
</div> </div>
{activeCycle.assignees.length > 0 && ( {activeCycle.assignee_ids.length > 0 && (
<div className="flex items-center gap-1 text-custom-text-200"> <div className="flex items-center gap-1 text-custom-text-200">
<AvatarGroup> <AvatarGroup>
{activeCycle.assignees.map((assignee) => ( {activeCycle.assignee_ids.map((assigne_id) => {
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} /> const member = getUserDetails(assigne_id);
))} return <Avatar key={member?.id} name={member?.display_name} src={member?.avatar} />;
})}
</AvatarGroup> </AvatarGroup>
</div> </div>
)} )}

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// hooks // hooks
import { useEventTracker, useCycle, useUser } from "hooks/store"; import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles"; import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
@ -40,6 +40,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { addCycleToFavorites, removeCycleFromFavorites, getCycleById } = useCycle(); const { addCycleToFavorites, removeCycleFromFavorites, getCycleById } = useCycle();
const { getUserDetails } = useMember();
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// computed // computed
@ -212,13 +213,14 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
<LayersIcon className="h-4 w-4 text-custom-text-300" /> <LayersIcon className="h-4 w-4 text-custom-text-300" />
<span className="text-xs text-custom-text-300">{issueCount}</span> <span className="text-xs text-custom-text-300">{issueCount}</span>
</div> </div>
{cycleDetails.assignees.length > 0 && ( {cycleDetails.assignee_ids.length > 0 && (
<Tooltip tooltipContent={`${cycleDetails.assignees.length} Members`}> <Tooltip tooltipContent={`${cycleDetails.assignee_ids.length} Members`}>
<div className="flex cursor-default items-center gap-1"> <div className="flex cursor-default items-center gap-1">
<AvatarGroup showTooltip={false}> <AvatarGroup showTooltip={false}>
{cycleDetails.assignees.map((assignee) => ( {cycleDetails.assignee_ids.map((assigne_id) => {
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} /> const member = getUserDetails(assigne_id);
))} return <Avatar key={member?.id} name={member?.display_name} src={member?.avatar} />;
})}
</AvatarGroup> </AvatarGroup>
</div> </div>
</Tooltip> </Tooltip>

View File

@ -3,7 +3,7 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// hooks // hooks
import { useEventTracker, useCycle, useUser } from "hooks/store"; import { useEventTracker, useCycle, useUser, useMember } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles"; import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles";
@ -44,6 +44,7 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { getCycleById, addCycleToFavorites, removeCycleFromFavorites } = useCycle(); const { getCycleById, addCycleToFavorites, removeCycleFromFavorites } = useCycle();
const { getUserDetails } = useMember();
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -230,13 +231,14 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
</div> </div>
<div className="relative flex flex-shrink-0 items-center gap-3"> <div className="relative flex flex-shrink-0 items-center gap-3">
<Tooltip tooltipContent={`${cycleDetails.assignees.length} Members`}> <Tooltip tooltipContent={`${cycleDetails.assignee_ids?.length} Members`}>
<div className="flex w-10 cursor-default items-center justify-center"> <div className="flex w-10 cursor-default items-center justify-center">
{cycleDetails.assignees.length > 0 ? ( {cycleDetails.assignee_ids?.length > 0 ? (
<AvatarGroup showTooltip={false}> <AvatarGroup showTooltip={false}>
{cycleDetails.assignees.map((assignee) => ( {cycleDetails.assignee_ids?.map((assigne_id) => {
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} /> const member = getUserDetails(assigne_id);
))} return <Avatar key={member?.id} name={member?.display_name} src={member?.avatar} />;
})}
</AvatarGroup> </AvatarGroup>
) : ( ) : (
<span className="flex h-5 w-5 items-end justify-center rounded-full border border-dashed border-custom-text-400 bg-custom-background-80"> <span className="flex h-5 w-5 items-end justify-center rounded-full border border-dashed border-custom-text-400 bg-custom-background-80">

View File

@ -36,7 +36,7 @@ export const CycleForm: React.FC<Props> = (props) => {
reset, reset,
} = useForm<ICycle>({ } = useForm<ICycle>({
defaultValues: { defaultValues: {
project: projectId, project_id: projectId,
name: data?.name || "", name: data?.name || "",
description: data?.description || "", description: data?.description || "",
start_date: data?.start_date || null, start_date: data?.start_date || null,
@ -61,13 +61,13 @@ export const CycleForm: React.FC<Props> = (props) => {
maxDate?.setDate(maxDate.getDate() - 1); maxDate?.setDate(maxDate.getDate() - 1);
return ( return (
<form onSubmit={handleSubmit((formData)=>handleFormSubmit(formData,dirtyFields))}> <form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
<div className="space-y-5"> <div className="space-y-5">
<div className="flex items-center gap-x-3"> <div className="flex items-center gap-x-3">
{!status && ( {!status && (
<Controller <Controller
control={control} control={control}
name="project" name="project_id"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<ProjectDropdown <ProjectDropdown
value={value} value={value}

View File

@ -40,7 +40,7 @@ export const CycleGanttBlock: React.FC<Props> = observer((props) => {
? "rgb(var(--color-text-200))" ? "rgb(var(--color-text-200))"
: "", : "",
}} }}
onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project}/cycles/${cycleDetails?.id}`)} onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project_id}/cycles/${cycleDetails?.id}`)}
> >
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" /> <div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<Tooltip <Tooltip
@ -78,7 +78,7 @@ export const CycleGanttSidebarBlock: React.FC<Props> = observer((props) => {
return ( return (
<div <div
className="relative flex h-full w-full items-center gap-2" className="relative flex h-full w-full items-center gap-2"
onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project}/cycles/${cycleDetails?.id}`)} onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project_id}/cycles/${cycleDetails?.id}`)}
> >
<ContrastIcon <ContrastIcon
className="h-5 w-5 flex-shrink-0" className="h-5 w-5 flex-shrink-0"

View File

@ -33,7 +33,7 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
const payload: any = { ...data }; const payload: any = { ...data };
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder; if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
await updateCycleDetails(workspaceSlug.toString(), cycle.project, cycle.id, payload); await updateCycleDetails(workspaceSlug.toString(), cycle.project_id, cycle.id, payload);
}; };
const blockFormat = (blocks: (ICycle | null)[]) => { const blockFormat = (blocks: (ICycle | null)[]) => {

View File

@ -40,7 +40,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const handleCreateCycle = async (payload: Partial<ICycle>) => { const handleCreateCycle = async (payload: Partial<ICycle>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const selectedProjectId = payload.project ?? projectId.toString(); const selectedProjectId = payload.project_id ?? projectId.toString();
await createCycle(workspaceSlug, selectedProjectId, payload) await createCycle(workspaceSlug, selectedProjectId, payload)
.then((res) => { .then((res) => {
setToastAlert({ setToastAlert({
@ -69,7 +69,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>, dirtyFields: any) => { const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>, dirtyFields: any) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const selectedProjectId = payload.project ?? projectId.toString(); const selectedProjectId = payload.project_id ?? projectId.toString();
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload) await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
.then((res) => { .then((res) => {
const changed_properties = Object.keys(dirtyFields); const changed_properties = Object.keys(dirtyFields);
@ -155,8 +155,8 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
// if data is present, set active project to the project of the // if data is present, set active project to the project of the
// issue. This has more priority than the project in the url. // issue. This has more priority than the project in the url.
if (data && data.project) { if (data && data.project_id) {
setActiveProject(data.project); setActiveProject(data.project_id);
return; return;
} }

View File

@ -45,7 +45,7 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
await deleteModule(workspaceSlug.toString(), projectId.toString(), data.id) await deleteModule(workspaceSlug.toString(), projectId.toString(), data.id)
.then(() => { .then(() => {
if (moduleId || peekModule) router.push(`/${workspaceSlug}/projects/${data.project}/modules`); if (moduleId || peekModule) router.push(`/${workspaceSlug}/projects/${data.project_id}/modules`);
handleClose(); handleClose();
setToastAlert({ setToastAlert({
type: "success", type: "success",

View File

@ -23,8 +23,8 @@ const defaultValues: Partial<IModule> = {
name: "", name: "",
description: "", description: "",
status: "backlog", status: "backlog",
lead: null, lead_id: null,
members: [], member_ids: [],
}; };
export const ModuleForm: React.FC<Props> = ({ export const ModuleForm: React.FC<Props> = ({
@ -43,12 +43,12 @@ export const ModuleForm: React.FC<Props> = ({
reset, reset,
} = useForm<IModule>({ } = useForm<IModule>({
defaultValues: { defaultValues: {
project: projectId, project_id: projectId,
name: data?.name || "", name: data?.name || "",
description: data?.description || "", description: data?.description || "",
status: data?.status || "backlog", status: data?.status || "backlog",
lead: data?.lead || null, lead_id: data?.lead_id || null,
members: data?.members || [], member_ids: data?.member_ids || [],
}, },
}); });
@ -83,7 +83,7 @@ export const ModuleForm: React.FC<Props> = ({
{!status && ( {!status && (
<Controller <Controller
control={control} control={control}
name="project" name="project_id"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<div className="h-7"> <div className="h-7">
<ProjectDropdown <ProjectDropdown
@ -184,7 +184,7 @@ export const ModuleForm: React.FC<Props> = ({
<ModuleStatusSelect control={control} error={errors.status} tabIndex={5} /> <ModuleStatusSelect control={control} error={errors.status} tabIndex={5} />
<Controller <Controller
control={control} control={control}
name="lead" name="lead_id"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<div className="h-7"> <div className="h-7">
<ProjectMemberDropdown <ProjectMemberDropdown
@ -201,7 +201,7 @@ export const ModuleForm: React.FC<Props> = ({
/> />
<Controller <Controller
control={control} control={control}
name="members" name="member_ids"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<div className="h-7"> <div className="h-7">
<ProjectMemberDropdown <ProjectMemberDropdown

View File

@ -29,7 +29,9 @@ export const ModuleGanttBlock: React.FC<Props> = observer((props) => {
<div <div
className="relative flex h-full w-full items-center rounded" className="relative flex h-full w-full items-center rounded"
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === moduleDetails?.status)?.color }} style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === moduleDetails?.status)?.color }}
onClick={() => router.push(`/${workspaceSlug}/projects/${moduleDetails?.project}/modules/${moduleDetails?.id}`)} onClick={() =>
router.push(`/${workspaceSlug}/projects/${moduleDetails?.project_id}/modules/${moduleDetails?.id}`)
}
> >
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" /> <div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<Tooltip <Tooltip
@ -65,7 +67,9 @@ export const ModuleGanttSidebarBlock: React.FC<Props> = observer((props) => {
return ( return (
<div <div
className="relative flex h-full w-full items-center gap-2" className="relative flex h-full w-full items-center gap-2"
onClick={() => router.push(`/${workspaceSlug}/projects/${moduleDetails?.project}/modules/${moduleDetails?.id}`)} onClick={() =>
router.push(`/${workspaceSlug}/projects/${moduleDetails?.project_id}/modules/${moduleDetails?.id}`)
}
> >
<ModuleStatusIcon status={moduleDetails?.status ?? "backlog"} height="16px" width="16px" /> <ModuleStatusIcon status={moduleDetails?.status ?? "backlog"} height="16px" width="16px" />
<h6 className="flex-grow truncate text-sm font-medium">{moduleDetails?.name}</h6> <h6 className="flex-grow truncate text-sm font-medium">{moduleDetails?.name}</h6>

View File

@ -22,7 +22,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
const payload: any = { ...data }; const payload: any = { ...data };
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder; if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
await updateModuleDetails(workspaceSlug.toString(), module.project, module.id, payload); await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload);
}; };
const blockFormat = (blocks: string[]) => const blockFormat = (blocks: string[]) =>

View File

@ -24,8 +24,8 @@ const defaultValues: Partial<IModule> = {
name: "", name: "",
description: "", description: "",
status: "backlog", status: "backlog",
lead: null, lead_id: null,
members: [], member_ids: [],
}; };
export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => { export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
@ -51,7 +51,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
const handleCreateModule = async (payload: Partial<IModule>) => { const handleCreateModule = async (payload: Partial<IModule>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
const selectedProjectId = payload.project ?? projectId.toString(); const selectedProjectId = payload.project_id ?? projectId.toString();
await createModule(workspaceSlug.toString(), selectedProjectId, payload) await createModule(workspaceSlug.toString(), selectedProjectId, payload)
.then((res) => { .then((res) => {
handleClose(); handleClose();
@ -81,7 +81,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
const handleUpdateModule = async (payload: Partial<IModule>, dirtyFields: any) => { const handleUpdateModule = async (payload: Partial<IModule>, dirtyFields: any) => {
if (!workspaceSlug || !projectId || !data) return; if (!workspaceSlug || !projectId || !data) return;
const selectedProjectId = payload.project ?? projectId.toString(); const selectedProjectId = payload.project_id ?? projectId.toString();
await updateModuleDetails(workspaceSlug.toString(), selectedProjectId, data.id, payload) await updateModuleDetails(workspaceSlug.toString(), selectedProjectId, data.id, payload)
.then((res) => { .then((res) => {
handleClose(); handleClose();
@ -129,8 +129,8 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
// if data is present, set active project to the project of the // if data is present, set active project to the project of the
// issue. This has more priority than the project in the url. // issue. This has more priority than the project in the url.
if (data && data.project) { if (data && data.project_id) {
setActiveProject(data.project); setActiveProject(data.project_id);
return; return;
} }

View File

@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// hooks // hooks
import { useEventTracker, useModule, useUser } from "hooks/store"; import { useEventTracker, useMember, useModule, useUser } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules";
@ -37,6 +37,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule(); const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
const { getUserDetails } = useMember();
const { setTrackElement, captureEvent } = useEventTracker(); const { setTrackElement, captureEvent } = useEventTracker();
// derived values // derived values
const moduleDetails = getModuleById(moduleId); const moduleDetails = getModuleById(moduleId);
@ -147,8 +148,8 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
? !moduleTotalIssues || moduleTotalIssues === 0 ? !moduleTotalIssues || moduleTotalIssues === 0
? "0 Issue" ? "0 Issue"
: moduleTotalIssues === moduleDetails.completed_issues : moduleTotalIssues === moduleDetails.completed_issues
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? "s" : ""}` ? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? "s" : ""}`
: `${moduleDetails.completed_issues}/${moduleTotalIssues} Issues` : `${moduleDetails.completed_issues}/${moduleTotalIssues} Issues`
: "0 Issue"; : "0 Issue";
return ( return (
@ -163,7 +164,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
/> />
)} )}
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} /> <DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${moduleDetails.project}/modules/${moduleDetails.id}`}> <Link href={`/${workspaceSlug}/projects/${moduleDetails.project_id}/modules/${moduleDetails.id}`}>
<div className="flex h-44 w-full flex-col justify-between rounded border border-custom-border-100 bg-custom-background-100 p-4 text-sm hover:shadow-md"> <div className="flex h-44 w-full flex-col justify-between rounded border border-custom-border-100 bg-custom-background-100 p-4 text-sm hover:shadow-md">
<div> <div>
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
@ -195,13 +196,14 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
<LayersIcon className="h-4 w-4 text-custom-text-300" /> <LayersIcon className="h-4 w-4 text-custom-text-300" />
<span className="text-xs text-custom-text-300">{issueCount ?? "0 Issue"}</span> <span className="text-xs text-custom-text-300">{issueCount ?? "0 Issue"}</span>
</div> </div>
{moduleDetails.members_detail.length > 0 && ( {moduleDetails.member_ids?.length > 0 && (
<Tooltip tooltipContent={`${moduleDetails.members_detail.length} Members`}> <Tooltip tooltipContent={`${moduleDetails.member_ids.length} Members`}>
<div className="flex cursor-default items-center gap-1"> <div className="flex cursor-default items-center gap-1">
<AvatarGroup showTooltip={false}> <AvatarGroup showTooltip={false}>
{moduleDetails.members_detail.map((member) => ( {moduleDetails.member_ids.map((member_id) => {
<Avatar key={member.id} name={member.display_name} src={member.avatar} /> const member = getUserDetails(member_id);
))} return <Avatar key={member?.id} name={member?.display_name} src={member?.avatar} />;
})}
</AvatarGroup> </AvatarGroup>
</div> </div>
</Tooltip> </Tooltip>

View File

@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// hooks // hooks
import { useModule, useUser, useEventTracker } from "hooks/store"; import { useModule, useUser, useEventTracker, useMember } from "hooks/store";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules"; import { CreateUpdateModuleModal, DeleteModuleModal } from "components/modules";
@ -37,6 +37,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule(); const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
const { getUserDetails } = useMember();
const { setTrackElement, captureEvent } = useEventTracker(); const { setTrackElement, captureEvent } = useEventTracker();
// derived values // derived values
const moduleDetails = getModuleById(moduleId); const moduleDetails = getModuleById(moduleId);
@ -153,7 +154,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
/> />
)} )}
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} /> <DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
<Link href={`/${workspaceSlug}/projects/${moduleDetails.project}/modules/${moduleDetails.id}`}> <Link href={`/${workspaceSlug}/projects/${moduleDetails.project_id}/modules/${moduleDetails.id}`}>
<div className="group flex w-full items-center justify-between gap-5 border-b border-custom-border-100 bg-custom-background-100 flex-col sm:flex-row px-5 py-6 text-sm hover:bg-custom-background-90"> <div className="group flex w-full items-center justify-between gap-5 border-b border-custom-border-100 bg-custom-background-100 flex-col sm:flex-row px-5 py-6 text-sm hover:bg-custom-background-90">
<div className="relative flex w-full items-center gap-3 justify-between overflow-hidden"> <div className="relative flex w-full items-center gap-3 justify-between overflow-hidden">
<div className="relative w-full flex items-center gap-3 overflow-hidden"> <div className="relative w-full flex items-center gap-3 overflow-hidden">
@ -206,13 +207,14 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
</div> </div>
<div className="flex-shrink-0 relative flex items-center gap-3"> <div className="flex-shrink-0 relative flex items-center gap-3">
<Tooltip tooltipContent={`${moduleDetails.members_detail.length} Members`}> <Tooltip tooltipContent={`${moduleDetails.member_ids.length} Members`}>
<div className="flex w-10 cursor-default items-center justify-center gap-1"> <div className="flex w-10 cursor-default items-center justify-center gap-1">
{moduleDetails.members_detail.length > 0 ? ( {moduleDetails.member_ids.length > 0 ? (
<AvatarGroup showTooltip={false}> <AvatarGroup showTooltip={false}>
{moduleDetails.members_detail.map((member) => ( {moduleDetails.member_ids.map((member_id) => {
<Avatar key={member.id} name={member.display_name} src={member.avatar} /> const member = getUserDetails(member_id);
))} return <Avatar key={member?.id} name={member?.display_name} src={member?.avatar} />;
})}
</AvatarGroup> </AvatarGroup>
) : ( ) : (
<span className="flex h-5 w-5 items-end justify-center rounded-full border border-dashed border-custom-text-400 bg-custom-background-80"> <span className="flex h-5 w-5 items-end justify-center rounded-full border border-dashed border-custom-text-400 bg-custom-background-80">

View File

@ -37,8 +37,8 @@ import { EUserProjectRoles } from "constants/project";
import { MODULE_LINK_CREATED, MODULE_LINK_DELETED, MODULE_LINK_UPDATED, MODULE_UPDATED } from "constants/event-tracker"; import { MODULE_LINK_CREATED, MODULE_LINK_DELETED, MODULE_LINK_UPDATED, MODULE_UPDATED } from "constants/event-tracker";
const defaultValues: Partial<IModule> = { const defaultValues: Partial<IModule> = {
lead: "", lead_id: "",
members: [], member_ids: [],
start_date: null, start_date: null,
target_date: null, target_date: null,
status: "backlog", status: "backlog",
@ -323,8 +323,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<CustomSelect <CustomSelect
customButton={ customButton={
<span <span
className={`flex h-6 w-20 items-center justify-center rounded-sm text-center text-xs ${isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed" className={`flex h-6 w-20 items-center justify-center rounded-sm text-center text-xs ${
}`} isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
style={{ style={{
color: moduleStatus ? moduleStatus.color : "#a3a3a2", color: moduleStatus ? moduleStatus.color : "#a3a3a2",
backgroundColor: moduleStatus ? `${moduleStatus.color}20` : "#a3a3a220", backgroundColor: moduleStatus ? `${moduleStatus.color}20` : "#a3a3a220",
@ -373,13 +374,15 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<> <>
<Popover.Button <Popover.Button
ref={startDateButtonRef} ref={startDateButtonRef}
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed" className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
}`} isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
> >
<span <span
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${watch("start_date") ? "" : "text-custom-text-400" className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
}`} watch("start_date") ? "" : "text-custom-text-400"
}`}
> >
{renderFormattedDate(startDate) ?? "No date selected"} {renderFormattedDate(startDate) ?? "No date selected"}
</span> </span>
@ -427,13 +430,15 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<> <>
<Popover.Button <Popover.Button
ref={endDateButtonRef} ref={endDateButtonRef}
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed" className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
}`} isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
> >
<span <span
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${watch("target_date") ? "" : "text-custom-text-400" className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
}`} watch("target_date") ? "" : "text-custom-text-400"
}`}
> >
{renderFormattedDate(endDate) ?? "No date selected"} {renderFormattedDate(endDate) ?? "No date selected"}
</span> </span>
@ -485,13 +490,13 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
</div> </div>
<Controller <Controller
control={control} control={control}
name="lead" name="lead_id"
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<div className="w-1/2"> <div className="w-1/2">
<ProjectMemberDropdown <ProjectMemberDropdown
value={value ?? null} value={value ?? null}
onChange={(val) => { onChange={(val) => {
submitChanges({ lead: val }); submitChanges({ lead_id: val });
}} }}
projectId={projectId?.toString() ?? ""} projectId={projectId?.toString() ?? ""}
multiple={false} multiple={false}
@ -509,13 +514,13 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
</div> </div>
<Controller <Controller
control={control} control={control}
name="members" name="member_ids"
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<div className="w-1/2"> <div className="w-1/2">
<ProjectMemberDropdown <ProjectMemberDropdown
value={value ?? []} value={value ?? []}
onChange={(val: string[]) => { onChange={(val: string[]) => {
submitChanges({ members: val }); submitChanges({ member_ids: val });
}} }}
multiple multiple
projectId={projectId?.toString() ?? ""} projectId={projectId?.toString() ?? ""}

View File

@ -102,7 +102,7 @@ export class CycleStore implements ICycleStore {
get currentProjectCycleIds() { get currentProjectCycleIds() {
const projectId = this.rootStore.app.router.projectId; const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project === projectId); let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project_id === projectId);
allCycles = sortBy(allCycles, [(c) => c.sort_order]); allCycles = sortBy(allCycles, [(c) => c.sort_order]);
const allCycleIds = allCycles.map((c) => c.id); const allCycleIds = allCycles.map((c) => c.id);
return allCycleIds; return allCycleIds;
@ -116,7 +116,7 @@ export class CycleStore implements ICycleStore {
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let completedCycles = Object.values(this.cycleMap ?? {}).filter((c) => { let completedCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const hasEndDatePassed = isPast(new Date(c.end_date ?? "")); const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
return c.project === projectId && hasEndDatePassed; return c.project_id === projectId && hasEndDatePassed;
}); });
completedCycles = sortBy(completedCycles, [(c) => c.sort_order]); completedCycles = sortBy(completedCycles, [(c) => c.sort_order]);
const completedCycleIds = completedCycles.map((c) => c.id); const completedCycleIds = completedCycles.map((c) => c.id);
@ -131,7 +131,7 @@ export class CycleStore implements ICycleStore {
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let upcomingCycles = Object.values(this.cycleMap ?? {}).filter((c) => { let upcomingCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const isStartDateUpcoming = isFuture(new Date(c.start_date ?? "")); const isStartDateUpcoming = isFuture(new Date(c.start_date ?? ""));
return c.project === projectId && isStartDateUpcoming; return c.project_id === projectId && isStartDateUpcoming;
}); });
upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]); upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]);
const upcomingCycleIds = upcomingCycles.map((c) => c.id); const upcomingCycleIds = upcomingCycles.map((c) => c.id);
@ -146,7 +146,7 @@ export class CycleStore implements ICycleStore {
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let incompleteCycles = Object.values(this.cycleMap ?? {}).filter((c) => { let incompleteCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const hasEndDatePassed = isPast(new Date(c.end_date ?? "")); const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
return c.project === projectId && !hasEndDatePassed; return c.project_id === projectId && !hasEndDatePassed;
}); });
incompleteCycles = sortBy(incompleteCycles, [(c) => c.sort_order]); incompleteCycles = sortBy(incompleteCycles, [(c) => c.sort_order]);
const incompleteCycleIds = incompleteCycles.map((c) => c.id); const incompleteCycleIds = incompleteCycles.map((c) => c.id);
@ -160,7 +160,7 @@ export class CycleStore implements ICycleStore {
const projectId = this.rootStore.app.router.projectId; const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let draftCycles = Object.values(this.cycleMap ?? {}).filter( let draftCycles = Object.values(this.cycleMap ?? {}).filter(
(c) => c.project === projectId && !c.start_date && !c.end_date (c) => c.project_id === projectId && !c.start_date && !c.end_date
); );
draftCycles = sortBy(draftCycles, [(c) => c.sort_order]); draftCycles = sortBy(draftCycles, [(c) => c.sort_order]);
const draftCycleIds = draftCycles.map((c) => c.id); const draftCycleIds = draftCycles.map((c) => c.id);
@ -174,7 +174,7 @@ export class CycleStore implements ICycleStore {
const projectId = this.rootStore.app.router.projectId; const projectId = this.rootStore.app.router.projectId;
if (!projectId) return null; if (!projectId) return null;
const activeCycle = Object.keys(this.activeCycleIdMap ?? {}).find( const activeCycle = Object.keys(this.activeCycleIdMap ?? {}).find(
(cycleId) => this.cycleMap?.[cycleId]?.project === projectId (cycleId) => this.cycleMap?.[cycleId]?.project_id === projectId
); );
return activeCycle || null; return activeCycle || null;
} }
@ -202,7 +202,7 @@ export class CycleStore implements ICycleStore {
getProjectCycleIds = computedFn((projectId: string): string[] | null => { getProjectCycleIds = computedFn((projectId: string): string[] | null => {
if (!this.fetchedMap[projectId]) return null; if (!this.fetchedMap[projectId]) return null;
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project === projectId); let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project_id === projectId);
cycles = sortBy(cycles, [(c) => c.sort_order]); cycles = sortBy(cycles, [(c) => c.sort_order]);
const cycleIds = cycles.map((c) => c.id); const cycleIds = cycles.map((c) => c.id);
return cycleIds || null; return cycleIds || null;

View File

@ -99,7 +99,7 @@ export class ModulesStore implements IModuleStore {
get projectModuleIds() { get projectModuleIds() {
const projectId = this.rootStore.app.router.projectId; const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null; if (!projectId || !this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId); let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId);
projectModules = sortBy(projectModules, [(m) => m.sort_order]); projectModules = sortBy(projectModules, [(m) => m.sort_order]);
const projectModuleIds = projectModules.map((m) => m.id); const projectModuleIds = projectModules.map((m) => m.id);
return projectModuleIds || null; return projectModuleIds || null;
@ -119,7 +119,7 @@ export class ModulesStore implements IModuleStore {
getProjectModuleIds = computedFn((projectId: string) => { getProjectModuleIds = computedFn((projectId: string) => {
if (!this.fetchedMap[projectId]) return null; if (!this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId); let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId);
projectModules = sortBy(projectModules, [(m) => m.sort_order]); projectModules = sortBy(projectModules, [(m) => m.sort_order]);
const projectModuleIds = projectModules.map((m) => m.id); const projectModuleIds = projectModules.map((m) => m.id);
return projectModuleIds; return projectModuleIds;