2022-12-13 15:52:44 +00:00
// react
2022-11-19 14:21:26 +00:00
import React , { useEffect , useState } from "react" ;
// next
import { useRouter } from "next/router" ;
import type { NextPage } from "next" ;
// swr
2022-12-13 15:52:44 +00:00
import useSWR from "swr" ;
2022-11-19 14:21:26 +00:00
// services
2022-12-12 04:49:52 +00:00
import sprintService from "lib/services/cycles.service" ;
2022-11-19 14:21:26 +00:00
// hooks
import useUser from "lib/hooks/useUser" ;
2022-12-13 15:52:44 +00:00
import useIssuesProperties from "lib/hooks/useIssuesProperties" ;
2022-11-19 14:21:26 +00:00
// fetching keys
2022-12-13 15:52:44 +00:00
import { CYCLE_LIST } from "constants/fetch-keys" ;
2022-12-08 14:59:12 +00:00
// hoc
import withAuth from "lib/hoc/withAuthWrapper" ;
2022-11-19 14:21:26 +00:00
// layouts
2022-12-13 15:52:44 +00:00
import AppLayout from "layouts/app-layout" ;
2022-11-19 14:21:26 +00:00
// components
2022-12-13 15:52:44 +00:00
import CycleIssuesListModal from "components/project/cycles/CycleIssuesListModal" ;
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion" ;
2022-11-19 14:21:26 +00:00
import ConfirmSprintDeletion from "components/project/cycles/ConfirmCycleDeletion" ;
import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal" ;
import CreateUpdateSprintsModal from "components/project/cycles/CreateUpdateCyclesModal" ;
2022-12-15 04:17:56 +00:00
import CycleStatsView from "components/project/cycles/stats-view" ;
2022-12-13 15:52:44 +00:00
// headless ui
import { Popover , Transition } from "@headlessui/react" ;
2022-11-19 14:21:26 +00:00
// ui
2022-11-29 14:19:39 +00:00
import { BreadcrumbItem , Breadcrumbs , HeaderButton , Spinner , EmptySpace , EmptySpaceItem } from "ui" ;
2022-11-19 14:21:26 +00:00
// icons
2022-12-13 15:52:44 +00:00
import { ArrowPathIcon , ChevronDownIcon , PlusIcon } from "@heroicons/react/24/outline" ;
2022-11-19 14:21:26 +00:00
// types
2022-12-13 15:52:44 +00:00
import { IIssue , ICycle , SelectSprintType , SelectIssue , Properties } from "types" ;
// constants
import { classNames , replaceUnderscoreIfSnakeCase } from "constants/common" ;
2022-11-19 14:21:26 +00:00
const ProjectSprints : NextPage = ( ) = > {
const [ isOpen , setIsOpen ] = useState ( false ) ;
const [ selectedSprint , setSelectedSprint ] = useState < SelectSprintType > ( ) ;
const [ isIssueModalOpen , setIsIssueModalOpen ] = useState ( false ) ;
const [ selectedIssues , setSelectedIssues ] = useState < SelectIssue > ( ) ;
const [ deleteIssue , setDeleteIssue ] = useState < string | undefined > ( ) ;
2022-12-13 15:52:44 +00:00
const [ cycleIssuesListModal , setCycleIssuesListModal ] = useState ( false ) ;
const [ cycleId , setCycleId ] = useState ( "" ) ;
2022-11-19 14:21:26 +00:00
2022-12-06 14:38:28 +00:00
const { activeWorkspace , activeProject , issues } = useUser ( ) ;
2022-11-19 14:21:26 +00:00
const router = useRouter ( ) ;
const { projectId } = router . query ;
2022-11-29 14:19:39 +00:00
const { data : cycles } = useSWR < ICycle [ ] > (
2022-12-13 15:52:44 +00:00
activeWorkspace && projectId ? CYCLE_LIST ( projectId as string ) : null ,
2022-11-19 14:21:26 +00:00
activeWorkspace && projectId
? ( ) = > sprintService . getCycles ( activeWorkspace . slug , projectId as string )
: null
) ;
2022-12-13 15:52:44 +00:00
const [ properties , setProperties ] = useIssuesProperties (
activeWorkspace ? . slug ,
projectId as string
) ;
const openCreateIssueModal = (
2022-11-29 14:19:39 +00:00
cycleId : string ,
2022-11-19 14:21:26 +00:00
issue? : IIssue ,
actionType : "create" | "edit" | "delete" = "create"
) = > {
2022-11-29 14:19:39 +00:00
const cycle = cycles ? . find ( ( cycle ) = > cycle . id === cycleId ) ;
if ( cycle ) {
2022-11-19 14:21:26 +00:00
setSelectedSprint ( {
2022-11-29 14:19:39 +00:00
. . . cycle ,
2022-11-19 14:21:26 +00:00
actionType : "create-issue" ,
} ) ;
if ( issue ) setSelectedIssues ( { . . . issue , actionType } ) ;
setIsIssueModalOpen ( true ) ;
}
} ;
2022-12-13 15:52:44 +00:00
const openIssuesListModal = ( cycleId : string ) = > {
setCycleId ( cycleId ) ;
setCycleIssuesListModal ( true ) ;
2022-12-06 14:38:28 +00:00
} ;
2022-11-19 14:21:26 +00:00
useEffect ( ( ) = > {
if ( isOpen ) return ;
const timer = setTimeout ( ( ) = > {
setSelectedSprint ( undefined ) ;
clearTimeout ( timer ) ;
} , 500 ) ;
} , [ isOpen ] ) ;
useEffect ( ( ) = > {
if ( selectedIssues ? . actionType === "delete" ) {
setDeleteIssue ( selectedIssues . id ) ;
}
} , [ selectedIssues ] ) ;
return (
2022-12-07 23:27:10 +00:00
< AppLayout
2022-11-19 14:21:26 +00:00
meta = { {
title : "Plane - Cycles" ,
} }
2022-12-13 15:52:44 +00:00
breadcrumbs = {
< Breadcrumbs >
< BreadcrumbItem title = "Projects" link = "/projects" / >
< BreadcrumbItem title = { ` ${ activeProject ? . name ? ? "Project" } Cycles ` } / >
< / Breadcrumbs >
}
right = {
< div className = "flex items-center gap-2" >
< Popover className = "relative" >
{ ( { open } ) = > (
< >
< Popover.Button
className = { classNames (
open ? "bg-gray-100 text-gray-900" : "text-gray-500" ,
"group flex gap-2 items-center rounded-md bg-transparent text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none border p-2"
) }
>
< span > View < / span >
< ChevronDownIcon className = "h-4 w-4" aria-hidden = "true" / >
< / Popover.Button >
< Transition
as = { React . Fragment }
enter = "transition ease-out duration-200"
enterFrom = "opacity-0 translate-y-1"
enterTo = "opacity-100 translate-y-0"
leave = "transition ease-in duration-150"
leaveFrom = "opacity-100 translate-y-0"
leaveTo = "opacity-0 translate-y-1"
>
< Popover.Panel className = "absolute mr-5 right-1/2 z-10 mt-1 w-screen max-w-xs translate-x-1/2 transform p-4 bg-white rounded-lg shadow-lg overflow-hidden" >
< div className = "relative flex flex-col gap-1 gap-y-4" >
< div className = "relative flex flex-col gap-1" >
< h4 className = "text-base text-gray-600" > Properties < / h4 >
< div >
{ Object . keys ( properties ) . map ( ( key ) = > (
< button
key = { key }
type = "button"
className = { ` px-2 py-1 inline capitalize rounded border border-theme text-sm m-1 ${
properties [ key as keyof Properties ]
? "border-theme bg-theme text-white"
: ""
} ` }
onClick = { ( ) = > setProperties ( key as keyof Properties ) }
>
{ replaceUnderscoreIfSnakeCase ( key ) }
< / button >
) ) }
< / div >
< / div >
< / div >
< / Popover.Panel >
< / Transition >
< / >
) }
< / Popover >
< HeaderButton Icon = { PlusIcon } label = "Add Cycle" onClick = { ( ) = > setIsOpen ( true ) } / >
< / div >
}
2022-11-19 14:21:26 +00:00
>
< CreateUpdateSprintsModal
isOpen = {
isOpen &&
selectedSprint ? . actionType !== "delete" &&
selectedSprint ? . actionType !== "create-issue"
}
setIsOpen = { setIsOpen }
data = { selectedSprint }
projectId = { projectId as string }
/ >
< ConfirmSprintDeletion
isOpen = { isOpen && ! ! selectedSprint && selectedSprint . actionType === "delete" }
setIsOpen = { setIsOpen }
data = { selectedSprint }
/ >
< ConfirmIssueDeletion
handleClose = { ( ) = > setDeleteIssue ( undefined ) }
isOpen = { ! ! deleteIssue }
data = { selectedIssues }
/ >
< CreateUpdateIssuesModal
isOpen = {
isIssueModalOpen &&
selectedSprint ? . actionType === "create-issue" &&
selectedIssues ? . actionType !== "delete"
}
data = { selectedIssues }
prePopulateData = { { sprints : selectedSprint?.id } }
setIsOpen = { setIsOpen }
projectId = { projectId as string }
/ >
2022-12-13 15:52:44 +00:00
< CycleIssuesListModal
isOpen = { cycleIssuesListModal }
handleClose = { ( ) = > setCycleIssuesListModal ( false ) }
issues = { issues }
cycleId = { cycleId }
/ >
2022-11-29 14:19:39 +00:00
{ cycles ? (
cycles . length > 0 ? (
2022-12-13 15:52:44 +00:00
< div className = "space-y-5" >
2022-12-15 04:17:56 +00:00
< CycleStatsView cycles = { cycles } / >
2022-11-24 13:48:18 +00:00
< / div >
2022-11-19 14:21:26 +00:00
) : (
2022-11-24 13:48:18 +00:00
< div className = "w-full h-full flex flex-col justify-center items-center px-4" >
< EmptySpace
title = "You don't have any cycle yet."
description = "A cycle is a fixed time period where a team commits to a set number of issues from their backlog. Cycles are usually one, two, or four weeks long."
Icon = { ArrowPathIcon }
>
< EmptySpaceItem
title = "Create a new cycle"
description = {
< span >
Use < pre className = "inline bg-gray-100 px-2 py-1 rounded" > Ctrl / Command + Q < / pre > { " " }
shortcut to create a new cycle
< / span >
}
Icon = { PlusIcon }
action = { ( ) = > setIsOpen ( true ) }
/ >
< / EmptySpace >
2022-11-19 14:21:26 +00:00
< / div >
2022-11-24 13:48:18 +00:00
)
) : (
< div className = "w-full h-full flex justify-center items-center" >
< Spinner / >
< / div >
) }
2022-12-07 23:27:10 +00:00
< / AppLayout >
2022-11-19 14:21:26 +00:00
) ;
} ;
2022-12-08 14:59:12 +00:00
export default withAuth ( ProjectSprints ) ;