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" /> <div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
</Transition.Child> </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 <Transition.Child
as={React.Fragment} as={React.Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -214,7 +214,7 @@ export const CommandPalette: React.FC = () => {
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" 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 <Combobox
onChange={(value: any) => { onChange={(value: any) => {
if (value?.url) router.push(value.url); if (value?.url) router.push(value.url);

View File

@ -129,7 +129,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
{filteredIssues.length > 0 ? ( {filteredIssues.length > 0 ? (
<li className="p-2"> <li className="p-2">
{query === "" && ( {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 Select issues to add
</h2> </h2>
)} )}
@ -175,18 +175,6 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
</div> </div>
)} )}
</Combobox.Options> </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> </Combobox>
)} )}
/> />

View File

@ -14,6 +14,7 @@ import { CompletedCycleIcon } from "components/icons";
import { ICycle, SelectCycleType } from "types"; import { ICycle, SelectCycleType } from "types";
// fetch-keys // fetch-keys
import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys"; import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys";
import { Loader } from "components/ui";
export interface CompletedCyclesListProps { export interface CompletedCyclesListProps {
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>; setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
@ -48,8 +49,6 @@ export const CompletedCyclesList: React.FC<CompletedCyclesListProps> = ({
}; };
return ( return (
<>
{completedCycles && (
<> <>
<DeleteCycleModal <DeleteCycleModal
isOpen={ isOpen={
@ -60,7 +59,8 @@ export const CompletedCyclesList: React.FC<CompletedCyclesListProps> = ({
setIsOpen={setCycleDeleteModal} setIsOpen={setCycleDeleteModal}
data={selectedCycleForDelete} data={selectedCycleForDelete}
/> />
{completedCycles?.completed_cycles.length > 0 ? ( {completedCycles ? (
completedCycles.completed_cycles.length > 0 ? (
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
{completedCycles.completed_cycles.map((cycle) => ( {completedCycles.completed_cycles.map((cycle) => (
<SingleCycleCard <SingleCycleCard
@ -79,8 +79,13 @@ export const CompletedCyclesList: React.FC<CompletedCyclesListProps> = ({
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>. <pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3> </h3>
</div> </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"; import { CompletedCycleIcon, CurrentCycleIcon, UpcomingCycleIcon } from "components/icons";
// types // types
import { ICycle, SelectCycleType } from "types"; import { ICycle, SelectCycleType } from "types";
import { Loader } from "components/ui";
type TCycleStatsViewProps = { type TCycleStatsViewProps = {
cycles: ICycle[]; cycles: ICycle[] | undefined;
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>; setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
setSelectedCycle: React.Dispatch<React.SetStateAction<SelectCycleType>>; setSelectedCycle: React.Dispatch<React.SetStateAction<SelectCycleType>>;
type: "current" | "upcoming" | "completed"; type: "current" | "upcoming" | "draft";
}; };
export const CyclesList: React.FC<TCycleStatsViewProps> = ({ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
@ -44,7 +45,8 @@ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
setIsOpen={setCycleDeleteModal} setIsOpen={setCycleDeleteModal}
data={selectedCycleForDelete} data={selectedCycleForDelete}
/> />
{cycles.length > 0 ? ( {cycles ? (
cycles.length > 0 ? (
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
{cycles.map((cycle) => ( {cycles.map((cycle) => (
<SingleCycleCard <SingleCycleCard
@ -59,7 +61,7 @@ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
<div className="flex flex-col items-center justify-center gap-4 text-center"> <div className="flex flex-col items-center justify-center gap-4 text-center">
{type === "upcoming" ? ( {type === "upcoming" ? (
<UpcomingCycleIcon height="56" width="56" /> <UpcomingCycleIcon height="56" width="56" />
) : type === "completed" ? ( ) : type === "draft" ? (
<CompletedCycleIcon height="56" width="56" /> <CompletedCycleIcon height="56" width="56" />
) : ( ) : (
<CurrentCycleIcon height="56" width="56" /> <CurrentCycleIcon height="56" width="56" />
@ -69,6 +71,11 @@ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>. <pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
</h3> </h3>
</div> </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 await cycleService
.createCycle(workspaceSlug as string, projectId as string, payload) .createCycle(workspaceSlug as string, projectId as string, payload)
.then((res) => { .then((res) => {
switch ( switch (getDateRangeStatus(res.start_date, res.end_date)) {
res?.start_date && res.end_date
? getDateRangeStatus(res?.start_date, res.end_date)
: "draft"
) {
case "completed": case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string)); mutate(CYCLE_COMPLETE_LIST(projectId as string));
break; break;
@ -81,11 +77,7 @@ export const CreateUpdateCycleModal: React.FC<CycleModalProps> = ({
await cycleService await cycleService
.updateCycle(workspaceSlug as string, projectId as string, cycleId, payload) .updateCycle(workspaceSlug as string, projectId as string, cycleId, payload)
.then((res) => { .then((res) => {
switch ( switch (getDateRangeStatus(res.start_date, res.end_date)) {
res?.start_date && res.end_date
? getDateRangeStatus(res?.start_date, res.end_date)
: "draft"
) {
case "completed": case "completed":
mutate(CYCLE_COMPLETE_LIST(projectId as string)); mutate(CYCLE_COMPLETE_LIST(projectId as string));
break; break;

View File

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

View File

@ -231,7 +231,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <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 <Transition.Child
as={React.Fragment} as={React.Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"

View File

@ -89,7 +89,9 @@ export const timeAgo = (time: any) => {
return time; 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 now = new Date();
const start = new Date(startDate); const start = new Date(startDate);
const end = new Date(endDate); const end = new Date(endDate);
@ -101,12 +103,25 @@ export const getDateRangeStatus = (startDate: string , endDate: string ) => {
} else { } else {
return "upcoming"; return "upcoming";
} }
} };
export const renderShortDateWithYearFormat = (date: Date) => { 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 day = date.getDate();
const month = months[date.getMonth()]; const month = months[date.getMonth()];
const year = date.getFullYear(); 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> <h3 className="text-3xl font-semibold text-black">Current Cycle</h3>
<div className="space-y-5"> <div className="space-y-5">
<CyclesList <CyclesList
cycles={currentAndUpcomingCycles?.current_cycle ?? []} cycles={currentAndUpcomingCycles?.current_cycle}
setCreateUpdateCycleModal={setCreateUpdateCycleModal} setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle} setSelectedCycle={setSelectedCycle}
type="current" type="current"
@ -132,7 +132,7 @@ const ProjectCycles: NextPage = () => {
> >
<Tab <Tab
className={({ selected }) => 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 selected
? "border-theme bg-theme text-white" ? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray" : "border-gray-300 bg-white hover:bg-hover-gray"
@ -143,7 +143,7 @@ const ProjectCycles: NextPage = () => {
</Tab> </Tab>
<Tab <Tab
className={({ selected }) => 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 selected
? "border-theme bg-theme text-white" ? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray" : "border-gray-300 bg-white hover:bg-hover-gray"
@ -154,7 +154,7 @@ const ProjectCycles: NextPage = () => {
</Tab> </Tab>
<Tab <Tab
className={({ selected }) => 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 selected
? "border-theme bg-theme text-white" ? "border-theme bg-theme text-white"
: "border-gray-300 bg-white hover:bg-hover-gray" : "border-gray-300 bg-white hover:bg-hover-gray"
@ -167,7 +167,7 @@ const ProjectCycles: NextPage = () => {
<Tab.Panels> <Tab.Panels>
<Tab.Panel as="div" className="mt-8 space-y-5"> <Tab.Panel as="div" className="mt-8 space-y-5">
<CyclesList <CyclesList
cycles={currentAndUpcomingCycles?.upcoming_cycle ?? []} cycles={currentAndUpcomingCycles?.upcoming_cycle}
setCreateUpdateCycleModal={setCreateUpdateCycleModal} setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle} setSelectedCycle={setSelectedCycle}
type="upcoming" type="upcoming"
@ -181,10 +181,10 @@ const ProjectCycles: NextPage = () => {
</Tab.Panel> </Tab.Panel>
<Tab.Panel as="div" className="mt-8 space-y-5"> <Tab.Panel as="div" className="mt-8 space-y-5">
<CyclesList <CyclesList
cycles={draftCycles?.draft_cycles ?? []} cycles={draftCycles?.draft_cycles}
setCreateUpdateCycleModal={setCreateUpdateCycleModal} setCreateUpdateCycleModal={setCreateUpdateCycleModal}
setSelectedCycle={setSelectedCycle} setSelectedCycle={setSelectedCycle}
type="upcoming" type="draft"
/> />
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>

View File

@ -25,7 +25,6 @@ export interface CurrentAndUpcomingCyclesResponse {
upcoming_cycle: ICycle[]; upcoming_cycle: ICycle[];
} }
export interface DraftCyclesResponse { export interface DraftCyclesResponse {
draft_cycles: ICycle[]; draft_cycles: ICycle[];
} }