chore: cycles loading, fix: cycles favorite mutation (#379)

This commit is contained in:
Aaryan Khandelwal 2023-03-07 11:04:51 +05:30 committed by GitHub
parent b54a1f221f
commit 64978969a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 206 additions and 132 deletions

View File

@ -204,7 +204,7 @@ export const CommandPalette: React.FC = () => {
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
<div className="fixed inset-0 z-20 p-4 sm:p-6 md:p-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
@ -214,7 +214,7 @@ export const CommandPalette: React.FC = () => {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white bg-opacity-80 shadow-2xl ring-1 ring-black ring-opacity-5 backdrop-blur backdrop-filter transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
<Combobox
onChange={(value: any) => {
if (value?.url) router.push(value.url);

View File

@ -129,7 +129,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
{filteredIssues.length > 0 ? (
<li className="p-2">
{query === "" && (
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
<h2 className="mb-2 px-3 text-xs font-semibold text-gray-900">
Select issues to add
</h2>
)}
@ -175,18 +175,6 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
</div>
)}
</Combobox.Options>
{query !== "" && filteredIssues.length === 0 && (
<div className="py-14 px-6 text-center sm:px-14">
<RectangleStackIcon
className="mx-auto h-6 w-6 text-gray-900 text-opacity-40"
aria-hidden="true"
/>
<p className="mt-4 text-sm text-gray-900">
We couldn{"'"}t find any issue with that term. Please try again.
</p>
</div>
)}
</Combobox>
)}
/>

View File

@ -14,6 +14,7 @@ import { CompletedCycleIcon } from "components/icons";
import { ICycle, SelectCycleType } from "types";
// fetch-keys
import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys";
import { Loader } from "components/ui";
export interface CompletedCyclesListProps {
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
@ -49,38 +50,42 @@ export const CompletedCyclesList: React.FC<CompletedCyclesListProps> = ({
return (
<>
{completedCycles && (
<>
<DeleteCycleModal
isOpen={
cycleDeleteModal &&
!!selectedCycleForDelete &&
selectedCycleForDelete.actionType === "delete"
}
setIsOpen={setCycleDeleteModal}
data={selectedCycleForDelete}
/>
{completedCycles?.completed_cycles.length > 0 ? (
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
{completedCycles.completed_cycles.map((cycle) => (
<SingleCycleCard
key={cycle.id}
cycle={cycle}
handleDeleteCycle={() => handleDeleteCycle(cycle)}
handleEditCycle={() => handleEditCycle(cycle)}
/>
))}
</div>
) : (
<div className="flex flex-col items-center justify-center gap-4 text-center">
<CompletedCycleIcon height="56" width="56" />
<h3 className="text-gray-500">
No completed cycles yet. Create with{" "}
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3>
</div>
)}
</>
<DeleteCycleModal
isOpen={
cycleDeleteModal &&
!!selectedCycleForDelete &&
selectedCycleForDelete.actionType === "delete"
}
setIsOpen={setCycleDeleteModal}
data={selectedCycleForDelete}
/>
{completedCycles ? (
completedCycles.completed_cycles.length > 0 ? (
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
{completedCycles.completed_cycles.map((cycle) => (
<SingleCycleCard
key={cycle.id}
cycle={cycle}
handleDeleteCycle={() => handleDeleteCycle(cycle)}
handleEditCycle={() => handleEditCycle(cycle)}
/>
))}
</div>
) : (
<div className="flex flex-col items-center justify-center gap-4 text-center">
<CompletedCycleIcon height="56" width="56" />
<h3 className="text-gray-500">
No completed cycles yet. Create with{" "}
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3>
</div>
)
) : (
<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>
)}
</>
);

View File

@ -6,12 +6,13 @@ import { DeleteCycleModal, SingleCycleCard } from "components/cycles";
import { CompletedCycleIcon, CurrentCycleIcon, UpcomingCycleIcon } from "components/icons";
// types
import { ICycle, SelectCycleType } from "types";
import { Loader } from "components/ui";
type TCycleStatsViewProps = {
cycles: ICycle[];
cycles: ICycle[] | undefined;
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
setSelectedCycle: React.Dispatch<React.SetStateAction<SelectCycleType>>;
type: "current" | "upcoming" | "completed";
type: "current" | "upcoming" | "draft";
};
export const CyclesList: React.FC<TCycleStatsViewProps> = ({
@ -44,31 +45,37 @@ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
setIsOpen={setCycleDeleteModal}
data={selectedCycleForDelete}
/>
{cycles.length > 0 ? (
<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)}
/>
))}
</div>
{cycles ? (
cycles.length > 0 ? (
<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)}
/>
))}
</div>
) : (
<div className="flex flex-col items-center justify-center gap-4 text-center">
{type === "upcoming" ? (
<UpcomingCycleIcon height="56" width="56" />
) : type === "draft" ? (
<CompletedCycleIcon height="56" width="56" />
) : (
<CurrentCycleIcon height="56" width="56" />
)}
<h3 className="text-gray-500">
No {type} {type === "current" ? "cycle" : "cycles"} yet. Create with{" "}
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3>
</div>
)
) : (
<div className="flex flex-col items-center justify-center gap-4 text-center">
{type === "upcoming" ? (
<UpcomingCycleIcon height="56" width="56" />
) : type === "completed" ? (
<CompletedCycleIcon height="56" width="56" />
) : (
<CurrentCycleIcon height="56" width="56" />
)}
<h3 className="text-gray-500">
No {type} {type === "current" ? "cycle" : "cycles"} yet. Create with{" "}
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3>
</div>
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
<Loader.Item height="300px" />
</Loader>
)}
</>
);

View File

@ -43,11 +43,7 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
await cycleService
.createCycle(workspaceSlug as string, projectId as string, payload)
.then((res) => {
switch (
res?.start_date && res.end_date
? getDateRangeStatus(res?.start_date, res.end_date)
: "draft"
) {
switch (getDateRangeStatus(res.start_date, res.end_date)) {
case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string));
break;
@ -81,11 +77,7 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
await cycleService
.updateCycle(workspaceSlug as string, projectId as string, cycleId, payload)
.then((res) => {
switch (
res?.start_date && res.end_date
? getDateRangeStatus(res?.start_date, res.end_date)
: "draft"
) {
switch (getDateRangeStatus(res.start_date, res.end_date)) {
case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string));
break;

View File

@ -17,13 +17,25 @@ import { Disclosure, Transition } from "@headlessui/react";
import { CalendarDaysIcon } from "@heroicons/react/20/solid";
import { ChevronDownIcon, PencilIcon, StarIcon } from "@heroicons/react/24/outline";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { getDateRangeStatus, renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { groupBy } from "helpers/array.helper";
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
// types
import { CycleIssueResponse, ICycle } from "types";
import {
CompletedCyclesResponse,
CurrentAndUpcomingCyclesResponse,
CycleIssueResponse,
DraftCyclesResponse,
ICycle,
} from "types";
// fetch-keys
import { CYCLE_ISSUES, CYCLE_LIST } from "constants/fetch-keys";
import {
CYCLE_COMPLETE_LIST,
CYCLE_CURRENT_AND_UPCOMING_LIST,
CYCLE_DRAFT_LIST,
CYCLE_ISSUES,
CYCLE_LIST,
} from "constants/fetch-keys";
type TSingleStatProps = {
cycle: ICycle;
@ -75,15 +87,45 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = (props) => {
cycle: cycle.id,
})
.then(() => {
mutate<ICycle[]>(
CYCLE_LIST(projectId as string),
(prevData) =>
(prevData ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
false
);
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
if (cycleStatus === "current" || cycleStatus === "upcoming")
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
else if (cycleStatus === "completed")
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
else
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? true : c.is_favorite,
})),
}),
false
);
setToastAlert({
type: "success",
@ -106,15 +148,45 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = (props) => {
cyclesService
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
.then(() => {
mutate<ICycle[]>(
CYCLE_LIST(projectId as string),
(prevData) =>
(prevData ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
false
);
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
if (cycleStatus === "current" || cycleStatus === "upcoming")
mutate<CurrentAndUpcomingCyclesResponse>(
CYCLE_CURRENT_AND_UPCOMING_LIST(projectId as string),
(prevData) => ({
current_cycle: (prevData?.current_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
upcoming_cycle: (prevData?.upcoming_cycle ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
else if (cycleStatus === "completed")
mutate<CompletedCyclesResponse>(
CYCLE_COMPLETE_LIST(projectId as string),
(prevData) => ({
completed_cycles: (prevData?.completed_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
else
mutate<DraftCyclesResponse>(
CYCLE_DRAFT_LIST(projectId as string),
(prevData) => ({
draft_cycles: (prevData?.draft_cycles ?? []).map((c) => ({
...c,
is_favorite: c.id === cycle.id ? false : c.is_favorite,
})),
}),
false
);
setToastAlert({
type: "success",
@ -167,16 +239,12 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = (props) => {
</a>
</Link>
{cycle.is_favorite ? (
<button onClick={handleRemoveFromFavorites} className="p-1 ">
<span>
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
</span>
<button onClick={handleRemoveFromFavorites}>
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
</button>
) : (
<button onClick={handleAddToFavorites} className="p-1">
<span>
<StarIcon className="h-4 w-4 " color="#858E96" />
</span>
<button onClick={handleAddToFavorites}>
<StarIcon className="h-4 w-4 " color="#858E96" />
</button>
)}
</div>

View File

@ -231,7 +231,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="mt-10 flex min-h-full items-start justify-center p-4 text-center sm:p-0 md:mt-20">
<div className="mt-10 flex items-start justify-center p-4 text-center sm:p-0 md:mt-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"

View File

@ -89,7 +89,9 @@ export const timeAgo = (time: any) => {
return time;
};
export const getDateRangeStatus = (startDate: string , endDate: string ) => {
export const getDateRangeStatus = (startDate: string | null, endDate: string | null) => {
if (!startDate || !endDate) return "draft";
const now = new Date();
const start = new Date(startDate);
const end = new Date(endDate);
@ -101,12 +103,25 @@ export const getDateRangeStatus = (startDate: string , endDate: string ) => {
} else {
return "upcoming";
}
}
};
export const renderShortDateWithYearFormat = (date: Date) => {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const day = date.getDate();
const month = months[date.getMonth()];
const year = date.getFullYear();
return isNaN(date.getTime()) ? "N/A" : ` ${month} ${day}, ${year}`;
}
return isNaN(date.getTime()) ? "N/A" : ` ${month} ${day}, ${year}`;
};

View File

@ -115,7 +115,7 @@ const ProjectCycles: NextPage = () => {
<h3 className="text-3xl font-semibold text-black">Current Cycle</h3>
<div className="space-y-5">
<CyclesList
cycles={currentAndUpcomingCycles?.current_cycle ?? []}
cycles={currentAndUpcomingCycles?.current_cycle}
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle}
type="current"
@ -132,7 +132,7 @@ const ProjectCycles: NextPage = () => {
>
<Tab
className={({ selected }) =>
`rounded-3xl border px-5 py-1.5 text-sm sm:px-7 sm:py-2 sm:text-base ${
`rounded-3xl border px-5 py-1.5 text-sm outline-none sm:px-7 sm:py-2 sm:text-base ${
selected
? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray"
@ -143,7 +143,7 @@ const ProjectCycles: NextPage = () => {
</Tab>
<Tab
className={({ selected }) =>
`rounded-3xl border px-5 py-1.5 text-sm sm:px-7 sm:py-2 sm:text-base ${
`rounded-3xl border px-5 py-1.5 text-sm outline-none sm:px-7 sm:py-2 sm:text-base ${
selected
? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray"
@ -154,7 +154,7 @@ const ProjectCycles: NextPage = () => {
</Tab>
<Tab
className={({ selected }) =>
`rounded-3xl border px-5 py-1.5 text-sm sm:px-7 sm:py-2 sm:text-base ${
`rounded-3xl border px-5 py-1.5 text-sm outline-none sm:px-7 sm:py-2 sm:text-base ${
selected
? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray"
@ -167,7 +167,7 @@ const ProjectCycles: NextPage = () => {
<Tab.Panels>
<Tab.Panel as="div" className="mt-8 space-y-5">
<CyclesList
cycles={currentAndUpcomingCycles?.upcoming_cycle ?? []}
cycles={currentAndUpcomingCycles?.upcoming_cycle}
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle}
type="upcoming"
@ -181,10 +181,10 @@ const ProjectCycles: NextPage = () => {
</Tab.Panel>
<Tab.Panel as="div" className="mt-8 space-y-5">
<CyclesList
cycles={draftCycles?.draft_cycles ?? []}
cycles={draftCycles?.draft_cycles}
setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle}
type="upcoming"
type="draft"
/>
</Tab.Panel>
</Tab.Panels>

View File

@ -21,18 +21,17 @@ export interface ICycle {
}
export interface CurrentAndUpcomingCyclesResponse {
current_cycle : ICycle[];
upcoming_cycle : ICycle[];
current_cycle: ICycle[];
upcoming_cycle: ICycle[];
}
export interface DraftCyclesResponse {
draft_cycles : ICycle[];
}
draft_cycles: ICycle[];
}
export interface CompletedCyclesResponse {
completed_cycles : ICycle[];
}
completed_cycles: ICycle[];
}
export interface CycleIssueResponse {
id: string;