2023-05-28 20:44:40 +00:00
|
|
|
import React, { useState } from "react";
|
|
|
|
|
|
|
|
import { useRouter } from "next/router";
|
|
|
|
|
|
|
|
import { mutate } from "swr";
|
|
|
|
|
|
|
|
// services
|
|
|
|
import cyclesService from "services/cycles.service";
|
2023-05-17 07:28:01 +00:00
|
|
|
// hooks
|
2023-05-28 20:44:40 +00:00
|
|
|
import useToast from "hooks/use-toast";
|
2023-06-06 16:06:00 +00:00
|
|
|
import useUserAuth from "hooks/use-user-auth";
|
2023-05-17 07:28:01 +00:00
|
|
|
// components
|
|
|
|
import {
|
2023-05-28 20:44:40 +00:00
|
|
|
CreateUpdateCycleModal,
|
2023-05-20 12:00:15 +00:00
|
|
|
CyclesListGanttChartView,
|
2023-05-28 20:44:40 +00:00
|
|
|
DeleteCycleModal,
|
|
|
|
SingleCycleCard,
|
|
|
|
SingleCycleList,
|
2023-05-17 07:28:01 +00:00
|
|
|
} from "components/cycles";
|
|
|
|
// ui
|
2023-05-18 13:37:01 +00:00
|
|
|
import { EmptyState, Loader } from "components/ui";
|
2023-07-12 06:15:45 +00:00
|
|
|
// icons
|
|
|
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
2023-05-28 20:44:40 +00:00
|
|
|
// images
|
2023-07-12 06:15:45 +00:00
|
|
|
import emptyCycle from "public/empty-state/cycle.svg";
|
2023-05-28 20:44:40 +00:00
|
|
|
// helpers
|
|
|
|
import { getDateRangeStatus } from "helpers/date-time.helper";
|
2023-05-17 07:28:01 +00:00
|
|
|
// types
|
2023-05-28 20:44:40 +00:00
|
|
|
import { ICycle } from "types";
|
|
|
|
// fetch-keys
|
|
|
|
import {
|
2023-05-28 21:08:16 +00:00
|
|
|
COMPLETED_CYCLES_LIST,
|
|
|
|
CURRENT_CYCLE_LIST,
|
|
|
|
CYCLES_LIST,
|
|
|
|
DRAFT_CYCLES_LIST,
|
|
|
|
UPCOMING_CYCLES_LIST,
|
2023-05-28 20:44:40 +00:00
|
|
|
} from "constants/fetch-keys";
|
2023-05-17 07:28:01 +00:00
|
|
|
|
|
|
|
type Props = {
|
2023-05-28 20:44:40 +00:00
|
|
|
cycles: ICycle[] | undefined;
|
|
|
|
viewType: string | null;
|
2023-05-17 07:28:01 +00:00
|
|
|
};
|
|
|
|
|
2023-05-28 20:44:40 +00:00
|
|
|
export const CyclesView: React.FC<Props> = ({ cycles, viewType }) => {
|
|
|
|
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
|
|
|
const [selectedCycleToUpdate, setSelectedCycleToUpdate] = useState<ICycle | null>(null);
|
|
|
|
|
|
|
|
const [deleteCycleModal, setDeleteCycleModal] = useState(false);
|
|
|
|
const [selectedCycleToDelete, setSelectedCycleToDelete] = useState<ICycle | null>(null);
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
const { workspaceSlug, projectId } = router.query;
|
|
|
|
|
2023-06-06 16:06:00 +00:00
|
|
|
const { user } = useUserAuth();
|
2023-05-28 20:44:40 +00:00
|
|
|
const { setToastAlert } = useToast();
|
|
|
|
|
|
|
|
const handleEditCycle = (cycle: ICycle) => {
|
|
|
|
setSelectedCycleToUpdate(cycle);
|
|
|
|
setCreateUpdateCycleModal(true);
|
2023-05-17 07:28:01 +00:00
|
|
|
};
|
|
|
|
|
2023-05-28 20:44:40 +00:00
|
|
|
const handleDeleteCycle = (cycle: ICycle) => {
|
|
|
|
setSelectedCycleToDelete(cycle);
|
|
|
|
setDeleteCycleModal(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleAddToFavorites = (cycle: ICycle) => {
|
|
|
|
if (!workspaceSlug || !projectId) return;
|
|
|
|
|
|
|
|
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
|
|
|
|
|
|
|
const fetchKey =
|
|
|
|
cycleStatus === "current"
|
2023-05-28 21:08:16 +00:00
|
|
|
? CURRENT_CYCLE_LIST(projectId as string)
|
2023-05-28 20:44:40 +00:00
|
|
|
: cycleStatus === "upcoming"
|
2023-05-28 21:08:16 +00:00
|
|
|
? UPCOMING_CYCLES_LIST(projectId as string)
|
2023-05-28 20:44:40 +00:00
|
|
|
: cycleStatus === "completed"
|
2023-05-28 21:08:16 +00:00
|
|
|
? COMPLETED_CYCLES_LIST(projectId as string)
|
|
|
|
: DRAFT_CYCLES_LIST(projectId as string);
|
2023-05-28 20:44:40 +00:00
|
|
|
|
|
|
|
mutate<ICycle[]>(
|
|
|
|
fetchKey,
|
|
|
|
(prevData) =>
|
|
|
|
(prevData ?? []).map((c) => ({
|
|
|
|
...c,
|
|
|
|
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
|
|
|
})),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
mutate(
|
2023-05-28 21:08:16 +00:00
|
|
|
CYCLES_LIST(projectId as string),
|
2023-05-28 20:44:40 +00:00
|
|
|
(prevData: any) =>
|
|
|
|
(prevData ?? []).map((c: any) => ({
|
|
|
|
...c,
|
|
|
|
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
|
|
|
})),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
cyclesService
|
|
|
|
.addCycleToFavorites(workspaceSlug as string, projectId as string, {
|
|
|
|
cycle: cycle.id,
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
setToastAlert({
|
|
|
|
type: "error",
|
|
|
|
title: "Error!",
|
|
|
|
message: "Couldn't add the cycle to favorites. Please try again.",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleRemoveFromFavorites = (cycle: ICycle) => {
|
|
|
|
if (!workspaceSlug || !projectId) return;
|
|
|
|
|
|
|
|
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
|
|
|
|
|
|
|
const fetchKey =
|
|
|
|
cycleStatus === "current"
|
2023-05-28 21:08:16 +00:00
|
|
|
? CURRENT_CYCLE_LIST(projectId as string)
|
2023-05-28 20:44:40 +00:00
|
|
|
: cycleStatus === "upcoming"
|
2023-05-28 21:08:16 +00:00
|
|
|
? UPCOMING_CYCLES_LIST(projectId as string)
|
2023-05-28 20:44:40 +00:00
|
|
|
: cycleStatus === "completed"
|
2023-05-28 21:08:16 +00:00
|
|
|
? COMPLETED_CYCLES_LIST(projectId as string)
|
|
|
|
: DRAFT_CYCLES_LIST(projectId as string);
|
2023-05-28 20:44:40 +00:00
|
|
|
|
|
|
|
mutate<ICycle[]>(
|
|
|
|
fetchKey,
|
|
|
|
(prevData) =>
|
|
|
|
(prevData ?? []).map((c) => ({
|
|
|
|
...c,
|
|
|
|
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
|
|
|
})),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
mutate(
|
2023-05-28 21:08:16 +00:00
|
|
|
CYCLES_LIST(projectId as string),
|
2023-05-28 20:44:40 +00:00
|
|
|
(prevData: any) =>
|
|
|
|
(prevData ?? []).map((c: any) => ({
|
|
|
|
...c,
|
|
|
|
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
|
|
|
})),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
|
|
|
cyclesService
|
|
|
|
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
|
|
|
|
.catch(() => {
|
|
|
|
setToastAlert({
|
|
|
|
type: "error",
|
|
|
|
title: "Error!",
|
|
|
|
message: "Couldn't remove the cycle from favorites. Please try again.",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
2023-05-17 07:28:01 +00:00
|
|
|
|
|
|
|
return (
|
2023-05-20 12:00:15 +00:00
|
|
|
<>
|
2023-05-28 20:44:40 +00:00
|
|
|
<CreateUpdateCycleModal
|
|
|
|
isOpen={createUpdateCycleModal}
|
|
|
|
handleClose={() => setCreateUpdateCycleModal(false)}
|
|
|
|
data={selectedCycleToUpdate}
|
2023-06-06 16:06:00 +00:00
|
|
|
user={user}
|
2023-05-28 20:44:40 +00:00
|
|
|
/>
|
|
|
|
<DeleteCycleModal
|
|
|
|
isOpen={deleteCycleModal}
|
|
|
|
setIsOpen={setDeleteCycleModal}
|
|
|
|
data={selectedCycleToDelete}
|
2023-06-06 16:06:00 +00:00
|
|
|
user={user}
|
2023-05-28 20:44:40 +00:00
|
|
|
/>
|
|
|
|
{cycles ? (
|
|
|
|
cycles.length > 0 ? (
|
|
|
|
viewType === "list" ? (
|
2023-07-10 07:17:00 +00:00
|
|
|
<div className="divide-y divide-custom-border-100">
|
2023-05-28 20:44:40 +00:00
|
|
|
{cycles.map((cycle) => (
|
2023-07-10 07:17:00 +00:00
|
|
|
<div className="hover:bg-custom-background-80">
|
|
|
|
<div className="flex flex-col border-custom-border-100">
|
2023-05-28 20:44:40 +00:00
|
|
|
<SingleCycleList
|
|
|
|
key={cycle.id}
|
|
|
|
cycle={cycle}
|
|
|
|
handleDeleteCycle={() => handleDeleteCycle(cycle)}
|
|
|
|
handleEditCycle={() => handleEditCycle(cycle)}
|
|
|
|
handleAddToFavorites={() => handleAddToFavorites(cycle)}
|
|
|
|
handleRemoveFromFavorites={() => handleRemoveFromFavorites(cycle)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
) : viewType === "board" ? (
|
|
|
|
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
|
|
|
{cycles.map((cycle) => (
|
|
|
|
<SingleCycleCard
|
|
|
|
key={cycle.id}
|
|
|
|
cycle={cycle}
|
|
|
|
handleDeleteCycle={() => handleDeleteCycle(cycle)}
|
|
|
|
handleEditCycle={() => handleEditCycle(cycle)}
|
|
|
|
handleAddToFavorites={() => handleAddToFavorites(cycle)}
|
|
|
|
handleRemoveFromFavorites={() => handleRemoveFromFavorites(cycle)}
|
2023-05-20 12:00:15 +00:00
|
|
|
/>
|
2023-05-28 20:44:40 +00:00
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<CyclesListGanttChartView cycles={cycles ?? []} />
|
|
|
|
)
|
|
|
|
) : (
|
|
|
|
<EmptyState
|
2023-07-12 06:15:45 +00:00
|
|
|
title="Plan your project with cycles"
|
|
|
|
description="Cycle is a custom time period in which a team works to complete items on their backlog."
|
|
|
|
image={emptyCycle}
|
|
|
|
buttonText="New Cycle"
|
|
|
|
buttonIcon={<PlusIcon className="h-4 w-4" />}
|
|
|
|
onClick={() => {
|
|
|
|
const e = new KeyboardEvent("keydown", {
|
|
|
|
key: "q",
|
|
|
|
});
|
|
|
|
document.dispatchEvent(e);
|
|
|
|
}}
|
2023-05-28 20:44:40 +00:00
|
|
|
/>
|
|
|
|
)
|
|
|
|
) : viewType === "list" ? (
|
|
|
|
<Loader className="space-y-4">
|
|
|
|
<Loader.Item height="50px" />
|
|
|
|
<Loader.Item height="50px" />
|
|
|
|
<Loader.Item height="50px" />
|
|
|
|
</Loader>
|
|
|
|
) : viewType === "board" ? (
|
|
|
|
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
|
|
|
<Loader.Item height="200px" />
|
|
|
|
<Loader.Item height="200px" />
|
|
|
|
<Loader.Item height="200px" />
|
|
|
|
</Loader>
|
|
|
|
) : (
|
|
|
|
<Loader>
|
|
|
|
<Loader.Item height="300px" />
|
|
|
|
</Loader>
|
|
|
|
)}
|
2023-05-20 12:00:15 +00:00
|
|
|
</>
|
2023-05-17 07:28:01 +00:00
|
|
|
);
|
|
|
|
};
|