mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: cycles revamp using MobX (#2443)
* chore: kanban refactoring * chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd * chore: updated yarn lock * chore: updated the store for issues and issue filters * chore: resolved build error * chore: created filters and updated the issue filters, display_filter and display_properties in mobx and components * chore: implemented filters for issues * chore: UI theming updates * chore: handled single and multi select in filter cards * chore: implemented filters and views in kanaban * chore: updating filters, display_filter and display properties * chore: filter, layout, display filters, extra filters and display properties render validation * chore: clean up and resolved import warnings * chore: type check * chore: renamed gantt key to gantt_chart * chore: filter render UI and Functionality implementation * chore: filter empty state handling in issue filter selection * Implementing list view * chore: kanban drag drop logic * filtering * chore: store setup * chore: handled build issues * chore: store setup * user filter * chore: store setup * chore: store fixes and static data setup * chore: store setup for build fixes * fix: merge conflicts (#2231) * chore: dynamic position dropdown (#2138) * chore: dynamic position state dropdown for issue view * style: state select dropdown styling * fix: state icon attribute names * chore: state select dynamic dropdown * chore: member select dynamic dropdown * chore: label select dynamic dropdown * chore: priority select dynamic dropdown * chore: label select dropdown improvement * refactor: state dropdown location * chore: dropdown improvement and code refactor * chore: dynamic dropdown hook type added --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> * fix: fields not getting selected in the create issue form (#2212) * fix: hydration error and draft issue workflow * fix: build error * fix: properties getting de-selected after create, module & cycle not getting auto-select on the form * fix: display layout, props being updated directly * chore: sub issues count in individual issue (#2221) * fix: service imports * chore: rename csv service file --------- Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> * chore: store fixes * chore: update issue detail store to handle peek overview (#2237) * chore: dynamic position dropdown (#2138) * chore: dynamic position state dropdown for issue view * style: state select dropdown styling * fix: state icon attribute names * chore: state select dynamic dropdown * chore: member select dynamic dropdown * chore: label select dynamic dropdown * chore: priority select dynamic dropdown * chore: label select dropdown improvement * refactor: state dropdown location * chore: dropdown improvement and code refactor * chore: dynamic dropdown hook type added --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> * fix: fields not getting selected in the create issue form (#2212) * fix: hydration error and draft issue workflow * fix: build error * fix: properties getting de-selected after create, module & cycle not getting auto-select on the form * fix: display layout, props being updated directly * chore: sub issues count in individual issue (#2221) * Implemented nested issues in the sub issues section in issue detail page (#2233) * feat: subissues infinte level * feat: updated UI for sub issues * feat: subissues new ui and nested sub issues in issue detail * chore: removed repeated code * refactor: product updates modal layout (#2225) * fix: handle no issues in custom analytics (#2226) * fix: activity label color (#2227) * fix: profile issues layout switch (#2228) * chore: update service imports * chore: update issue detail store to handle peek overview --------- Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: guru_sainath <gurusainath007@gmail.com> * chore: minor fixes * workspace project fixes * feat: project issues topbar (#2256) * chore: project issues topbar * style: theming and minor UI fixes * refactor: file structure * chore: layout wise authorization added * style: filter dropdowns * chore: add fetch keys * chore: minor fixes * chore: filters dropdown (#2260) * chore: project issues topbar * style: theming and minor UI fixes * refactor: file structure * chore: layout wise authorization added * style: filter dropdowns * chore: add fetch keys * feat: search option for filters * fix: sticky headers * chore: sub_group_by section added * fix: leave project fixes * refactor: project card component refactor * Implemented swimlanes and kanban view (#2262) * chore: issue store for kanban and calendar * chore: updated ui for kanba and swimlanes * chore: yarn.lock updated * fix: computed filters logic * chore: added sub_group_by in params and handled sub-group-by render error in display filter's * fix: ui package setup and project update form refactor * fix: ui package setup * fix: minor ui fixes * dev: calendar view layout revamp (#2293) * dev: calendar view init * chore: new render logic * chore: implement calendar view * chore: calendar view * refactor: calendar payload * chore: remove active month logic from backend * chore: setup new store for calendar * refactor: issues fetching structure * chore: months dropdown * chore: modify request query params for calendar layout * refactor: remove console logs and add comments * chore: removed demo m-store routes * cycles changes * chore: issues grouped kanban and swimlanes UI and functionality (#2294) * chore: updated the all the group_by and sub_group_by UI and functionality render in kanban * chore: kanban sorting in mobx and ui updates * chore: ui changes and drag and drop functionality changes in kanban * chore: issues count render in kanban default and swimlanes * chore: Added icons to the group_by and sub_group_by in kanban and swimlanes * refactor: filter components, constants and helper functions (#2297) * refactor: filters and display filters to accept handlers as props * refactor: filters and display filters folder structure * refactor: change issue layout options constant structure * chore: display filters validations * chore: view less filters functionality * fix: display filters validation * refactor: wrap functions around useCallback * chore: start and target date filter options added * refactor: query params generator function * fix: query params generator function * dev: gantt chart implementation using MobX (#2302) * dev: fetch project gantt issues using mobx * chore: handle group by options in the kanban layout * dev: spreadsheet layout implementation using MobX (#2306) * dev: implement spreadsheet view using mobx * refactor: remove console logs and props * chore: refactoring cycles list * feat: adding additional ui components * dev: applied filters list implementation using MobX (#2325) * dev: applied filters list UI * fix: filter item height * chore: remove unnecessary classes * fix: params generator * fix: cycles views list and board * fix: cycles list rendering fixes * fix: layout fixes * refactor: filter components (#2359) * fix: calendar layout dividers * refactor: filter selection components * fix: dropdown closing after selection * refactor: filters components * chore: issue properties for list and kanban layouts and implemented estimates in project store (#2363) * chore: issue properties for state, priorit, labels and members * feat: implemented assignee, labels properties * fix: implemented estimates in project store and issue properties * chore: staer_date and due_date and validation properties in kanban * chore: filters import conflict * dev: setup module and module filter store (#2364) * dev: implement module issues using mobx store * dev: module filter store setup * chore: module store crud operations * chore: issue list layout (#2367) * chore: merge develop (#2388) * fix: build erros * chore: cycles, modules store integration, list and kanban layouts and updated kanban logic (#2399) * chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup * chore: cycles kanban and list view store * chore: cycles, modules kanban and list, kanban view store * refactor: change naming convention (#2383) * fix:auth layer revamp * chore: Implemented list and kanban views in project modules (#2402) * chore: updated kanban logic in project cycles and modules * chore: updated list and kanban in module * dev: implement global views using MobX (#2404) * fix: selfhosted fixes (#2154) * fix: selfhosted fixes * fix: updated env example * chore: dynamic position dropdown (#2138) * chore: dynamic position state dropdown for issue view * style: state select dropdown styling * fix: state icon attribute names * chore: state select dynamic dropdown * chore: member select dynamic dropdown * chore: label select dynamic dropdown * chore: priority select dynamic dropdown * chore: label select dropdown improvement * refactor: state dropdown location * chore: dropdown improvement and code refactor * chore: dynamic dropdown hook type added --------- Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> * fix: fields not getting selected in the create issue form (#2212) * fix: hydration error and draft issue workflow * fix: build error * fix: properties getting de-selected after create, module & cycle not getting auto-select on the form * fix: display layout, props being updated directly * chore: sub issues count in individual issue (#2221) * Implemented nested issues in the sub issues section in issue detail page (#2233) * feat: subissues infinte level * feat: updated UI for sub issues * feat: subissues new ui and nested sub issues in issue detail * chore: removed repeated code * refactor: product updates modal layout (#2225) * fix: handle no issues in custom analytics (#2226) * fix: activity label color (#2227) * fix: profile issues layout switch (#2228) * fix: issues resolved in sub issues (#2238) * fix: aws region name (#2234) * chore: updated docker naming conventions (#2239) * naming convention changes * dev: update docker-compose-hub in consistent with docker-compose * dev: updated docker container name --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> * chore: added state and priority order in workspace user profile (#2241) * fix: changed priority from None to none (#2229) * fix: cycle and module stats when issues are archived (#2185) * fix: cycle and module stats when issues are archived * fix: added draft filter --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * feat: quick add (#2240) * feat: quick add * style: made text color muted * chore: added epoch in draft (#2244) * chore: added epoch in draft * chore: removed extra spaces * fix: resolved pending issue graph in analytics, user wishes in dashboard, and typo in projects list (#2247) * style: settings page improvement (#2211) * style: settings page improvement * style: toggle switch styling --------- Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local> * chore: changed priority props in workspace and project (#2253) * fix: bug fix related to fetching dropdown options for the profile issue (#2246) * fix: sub issue state and member select build error (#2254) * rename view to layout (#2255) Co-authored-by: Your Name <you@example.com> * fix: bug fixes and ui improvement (#2250) * dev: remove auto filter endpoint * feat: quick-add placement in spreadsheet and gantt (#2259) * feat: sticking quick-add at the bottom of the screen fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page * fix: build error due to dynamic import * fix: draft issue delete not working (#2249) * fix: draft issue not deleting, project can't be changed in draft issue modal * fix: removed mutation for view where draft issues are not shown * fix: inline create issue for draft issue * fix: clearing data from localstorage on discard click * feat: Add peek overview in sub issues and updated UI for empty states. (#2263) * chore: add tooltip to show full time on activity logs (#2235) * fix: issue automation iterable error (#2208) * fix: n+1 queries for cycle list and project member endpoints (#2257) * [fix] nginx continuously rewriting and reloading on index page of spaces app (#2236) * chore: shifted index page to /home route * chore: added rewrite logic, to rewrite index to /home * chore: routed home to login route as login page * chore: updated nginx config to route to login * chore: updated path for home * dev: migration for 0.13 (#2266) * dev: updated migrations * dev: migration for 0.13 * dev: re-split migrations into two different files (#2268) * dev: split issue activity migration separate files * dev: resplit migrations into two different files * dev: changed the batch size * chore: udpate date filters to support dynamic options * fix: bugs in quick-add and draft issues (#2269) * fix: 'Last Drafted Issue' making sidebar look weird on collapsed * feat: scroll to the bottom when issue is created * fix: 'Add Issue' button overlapping issue card in spreadsheet view * fix: wrong placement of quick-add in calender layout * fix: spacing for issue card in spreadsheet view * chore: add instructions to contributing guide (#2270) * chore: add instructions to contributing guide * dev: update contributing.md to use the new configuration --------- Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> * fix: user dashboard greeting timezone (#2267) * chore: user greeting timezone * fix: group by labels not working on workspace level * feat: workspace global view, style: spreadsheet view revamp (#2273) * chore: workspace view types, services and hooks added * style: spreadsheet view revamp and code refactor * feat: workspace view * fix: build fix * chore: sidebar workspace issues redirection updated * style: gantt layout quick-add padding (#2272) * fix: 'Last Drafted Issue' making sidebar look weird on collapsed * feat: scroll to the bottom when issue is created * fix: 'Add Issue' button overlapping issue card in spreadsheet view * fix: wrong placement of quick-add in calender layout * fix: spacing for issue card in spreadsheet view * style: gantt layout quick-add padding style: removed 'State group' from draft issue * style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky * style: button color * fix: block click happening while moving (#2275) * dev: refactor date filters to a single function * chore: handle calendar date range in frontend (#2277) * chore: gantt chart empty state (#2279) * chore: gantt empty state * chore: Add heading to the gantt sidebar * style: calender quick-add same width as single date (#2280) * style: calender quick-add same width as single date * style: margin bottom in quick-add in spreadsheet view * fix: quick add opening in list-layout * style: reduced margin left * chore: updated created at in draft issue (#2278) * chore: make target dates inclusive when filtering (#2276) * chore: sort order and issue props for global views (#2283) * chore: removed project filter (#2284) * fix: inbox issue deletes (#2290) * chore: views (#2288) * chore: global views order by * chore: update permissions for global views --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * chore: fetch issues from previous and next month in the calendar view (#2282) * fix: issue activity estimate value bug fix (#2281) * fix: issue activity estimate value bug fix * fix: activity typo fix * fix: ui and bugs (#2289) * fix: 24 character limit on first & last name in onboarding page * fix: no option: 'Add Issue' in archive issue page * fix: in archive issue directly sending to issue detail page * fix: issue type showing in archive issue * fix: custom menu overflowing * fix: changing subscriber in filters has no effect * style: border in quick-add * fix: on onboarding member role overflowing * fix: inconsistent icons in issue detail * style: spacing, borders and shadows in quick-add * fix: custom menu truncate * fix: notifications for created by me and assigned to me (#2292) * chore: workspace view display filters and properties , code refactor (#2295) * chore: spreadsheet view context * chore: spreadsheet context provider * chore: spreadsheet view context * chore: display filters and properties added in workspace view and code refactor * fix: build error fix * chore: set sub-issue display option to false for global views --------- Co-authored-by: gurusainath <gurusainath007@gmail.com> * chore: label create error (#2299) * chore: global issues ui improvement and bug fixes (#2300) * chore: workspace view mutation fix ,bug fixes and code refactor (#2301) * chore: workspace view mutation fix ,bug fixes and code refactor * chore: update workspace view toast alert added * chore: workspace view order by removed (#2303) * dev: updated migrations for 0.13-dev (#2305) * chore: epoch migration batch size changed * chore: reoredered the migration files * dev: updated migrations for 0.13-dev * chore: added epoch field * dev: merged the migration files * fix: workspace view filters count fix (#2307) * fix: unsplash api fix (#2310) * fix: workspace view redirection fix, style: spreadsheet view shadow scroll fix (#2314) * fix: workspace view redirection fix * style: spreadsheet view scroll shadow fix * fix: update build workflow for the deploy app (#2315) * fix: workspace view add issue mutation fix (#2317) * dev: create action to sync PR changes to the repo (#2333) * fix: ui package readme added (#2334) * fix: variable name for token (#2336) * dev: update add permissions to the action (#2337) * dev: rename token variables (#2338) * fix: updated readme fixes (#2339) * dev: update sync workflow to run only when the source repo is configured (#2346) * dev: update sync workflow to run only when the source repo is configured * fix: naming convention changes --------- Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com> * fix: issue relation mutation and draft issue (#2340) * fix: issue relation mutation and draft issue * fix: 'New Issue' in gantt view fix: emoji select going under * fix: profile page typo * fix: sync workflow fixes (#2365) * fix: sync job pr description escaped values fix (#2366) * Update index.tsx (#2343) Fixes #2342 * dev: update apiserver configuration files (#2348) * dev: update apiserver configuration files * dev: add email and minio redirection urls * fix: themening validation in store init. (#2350) * chore: member can change role (#2371) * chore: removed the issue draft log from my profile (#2368) * adding sync info in pr title (#2373) * chore: layout access validation and switch in plane deploy issues route (#2351) * chore: handled route validation and layout access validation in plane deploy issues * chore: impoved validation condition * show current version in the help section dropdown (#2353) * fix: table menu positioning (#2354) * fix: handle cross project issues in the sub-issues. (#2357) * fix: login process validation based on api config (#2361) * dev: configuration endpoint for frontend client (#2355) * dev: configuration endpoint for frontend clients * dev: configuration enable magic and email/password signup * dev: update unsplash keys * dev: add unsplash API and add env for magic login * fix: 404 when redirecting user clicks on Sign In button (#2349) * fix: 404 when redirecting user to login page * fix: next_path redirection not working * fix: authentication workflow update in plane deploy --------- Co-authored-by: gurusainath <gurusainath007@gmail.com> * fix: project setting member role validation (#2369) * fix: project setting member role validation * chore: opacity removed from member setting page * chore: member setting page validation * chore: project covers endpoint (#2370) * chore: project covers endpoint * dev: remove print logs * dev: formatting --------- Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com> * feat: default project cover images tab on the change cover popover (#2375) * feat: default project cover images tab * chore: remove unnecessary env vars from turbo.json * chore: remove unnecessary OAuth envs (#2378) * chore: remove unnecessary oauth envs * merge conflicts resolved * fix: adding new service --------- Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com> * fix: added user store variables in mobx store observable (#2380) * fix: state group icons (#2381) * fix: removed default theme setting in the index page (#2382) * fix: removed default theme setting in the index page * fix: empty space * dev: global views and workspace filters store implemented * sync CE Master to EE Develop * refactor: create update view modal * chore: static issue global views * refactor: remove old code * refactor: filters select dropdown * chore: fix calendar layout * chore: mobx store for new applied filters * chore: dded search functionality --------- Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: guru_sainath <gurusainath007@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local> Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com> Co-authored-by: Thomas <git@thomasync.dev> Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com> Co-authored-by: Manish Gupta <manish@mgupta.me> * fix: Auth fixes and Layout fixes (#2408) * fix: auth fixes and layout improvements * fix: layout fixes * fix: analytics page fixes * dev: implemented project views using MobX (#2410) * dev: implemented project views list using mobx * style: views list UI * dev: implemented view issues page using mobx * refactor: project view issues fetching * chore: plane ui library component and code refactor (#2406) * chore: swap input component with plane/ui package * chore: swap textarea component with plane/ui package * chore: swap button component with plane/ui package * chore: button component revamp * fix: button type fix * chore: secondary button revamp * chore: button props updated * chore: swap loader component with plane/ui package * fix: build error fix * chore: button component refactor * chore: code refactor * chore: swap toggle switch component with plane/ui package * chore: swap spinner component with plane/ui package * chore: swap progress bar componenet with plan/ui package * chore: code refactor * chore: cycles revamp * fix: gitignore fixes * chore: updated cycles view adn layout mutation * fix: project card fixes * chore: ui component revamp (#2415) * chore: swap tooltip component with plane ui package * chore: swap linear progress component with plane ui package * fix: login button fix * chore: implement new worksapace wrapper for global views (#2412) * chore: implement new worksapace wrapper for global views pages * fix: merge conflicts * fix: merge conflicts * dev: add remaining layouts to cycle (#2413) * fix: workspace auth wrapper changes * chore: project card revamp and refactor (#2416) * removing dist from ui * chore: handled edit and delete operation in cycle board view, and gantt view * refactor: analytics (#2419) * refactor: helper functions * chore: updated all the page headers * refactor: custom analytics * refactor: project analytics modal * refactor: folder structure, remove junk code (#2423) * refactor: folder structure * chore: ad order by target date option * refactor: remove old layout components * refactor: inbox folder structure * fix: services fixes * fix: store imports changes * fix: services export fixes * fix: services implementation fixes * fix: build issue fixes * fix: react library fixes * refactor: MobX store folder structure (#2435) * refactor: store folder structure * chore: update import statements * fix: service import errors (#2436) * fix: service imports * chore: update service imports in store * chore: fix remianing service imports * build fixes * editor ts config fixes * fix: turbo and build fixes * fix: Auth screen loading implementation * fix: build issues * fix: turbo settings for ui package * chore: project active cycles requestes changed from swr to mobx * chore: imports and structuring codebase in cycles * chore: removed legacy code from cycles --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local> Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com> Co-authored-by: Thomas <git@thomasync.dev> Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com> Co-authored-by: Manish Gupta <manish@mgupta.me>
This commit is contained in:
parent
e684bda8b2
commit
a361dae185
@ -28,22 +28,14 @@ export const EmptyState: React.FC<Props> = ({
|
||||
isFullScreen = true,
|
||||
disabled = false,
|
||||
}) => (
|
||||
<div
|
||||
className={`h-full w-full mx-auto grid place-items-center p-8 ${
|
||||
isFullScreen ? "md:w-4/5 lg:w-3/5" : ""
|
||||
}`}
|
||||
>
|
||||
<div className={`h-full w-full mx-auto grid place-items-center p-8 ${isFullScreen ? "md:w-4/5 lg:w-3/5" : ""}`}>
|
||||
<div className="text-center flex flex-col items-center w-full">
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
|
||||
<h6 className="text-xl font-semibold mt-6 sm:mt-8 mb-3">{title}</h6>
|
||||
{description && <p className="text-custom-text-300 mb-7 sm:mb-8">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
{primaryButton && (
|
||||
<PrimaryButton
|
||||
className="flex items-center gap-1.5"
|
||||
onClick={primaryButton.onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<PrimaryButton className="flex items-center gap-1.5" onClick={primaryButton.onClick} disabled={disabled}>
|
||||
{primaryButton.icon}
|
||||
{primaryButton.text}
|
||||
</PrimaryButton>
|
||||
|
@ -1,14 +1,12 @@
|
||||
import React from "react";
|
||||
|
||||
import { MouseEvent } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { AssigneesList } from "components/ui/avatar";
|
||||
import { SingleProgressStats } from "components/core";
|
||||
@ -16,7 +14,7 @@ import { Loader, Tooltip, LinearProgressIndicator } from "@plane/ui";
|
||||
// components
|
||||
import ProgressChart from "components/core/sidebar/progress-chart";
|
||||
import { ActiveCycleProgressStats } from "components/cycles";
|
||||
|
||||
import { ViewIssueLabel } from "components/issues";
|
||||
// icons
|
||||
import { CalendarDaysIcon } from "@heroicons/react/20/solid";
|
||||
import { PriorityIcon } from "components/icons/priority-icon";
|
||||
@ -31,8 +29,6 @@ import {
|
||||
StateGroupIcon,
|
||||
} from "components/icons";
|
||||
import { StarIcon } from "@heroicons/react/24/outline";
|
||||
// components
|
||||
import { ViewIssueLabel } from "components/issues";
|
||||
// helpers
|
||||
import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
@ -69,34 +65,43 @@ const stateGroups = [
|
||||
},
|
||||
];
|
||||
|
||||
// services
|
||||
const cycleService = new CycleService();
|
||||
interface IActiveCycleDetails {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = (props) => {
|
||||
// services
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const ActiveCycleDetails: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: currentCycle } = useSWR(
|
||||
workspaceSlug && projectId ? CURRENT_CYCLE_LIST(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, "current")
|
||||
: null
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
|
||||
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null
|
||||
);
|
||||
const cycle = currentCycle ? currentCycle[0] : null;
|
||||
|
||||
const { data: issues } = useSWR(
|
||||
workspaceSlug && projectId && cycle?.id ? CYCLE_ISSUES_WITH_PARAMS(cycle?.id, { priority: "urgent,high" }) : null,
|
||||
workspaceSlug && projectId && cycle?.id
|
||||
? () =>
|
||||
cycleService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycle.id, {
|
||||
priority: "urgent,high",
|
||||
})
|
||||
: null
|
||||
) as { data: IIssue[] | undefined };
|
||||
const activeCycle = cycleStore.cycles?.[projectId] || null;
|
||||
const cycle = activeCycle ? activeCycle[0] : null;
|
||||
const issues = (cycleStore?.active_cycle_issues as any) || null;
|
||||
|
||||
if (!currentCycle)
|
||||
// const { data: issues } = useSWR(
|
||||
// workspaceSlug && projectId && cycle?.id ? CYCLE_ISSUES_WITH_PARAMS(cycle?.id, { priority: "urgent,high" }) : null,
|
||||
// workspaceSlug && projectId && cycle?.id
|
||||
// ? () =>
|
||||
// cycleService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycle.id, {
|
||||
// priority: "urgent,high",
|
||||
// })
|
||||
// : null
|
||||
// ) as { data: IIssue[] | undefined };
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Loader>
|
||||
<Loader.Item height="250px" />
|
||||
@ -146,70 +151,28 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
|
||||
const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date);
|
||||
|
||||
const handleAddToFavorites = () => {
|
||||
if (!workspaceSlug || !projectId || !cycle) return;
|
||||
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate<ICycle[]>(
|
||||
CURRENT_CYCLE_LIST(projectId as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((c) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
mutate(
|
||||
CYCLES_LIST(projectId as string),
|
||||
(prevData: any) =>
|
||||
(prevData ?? []).map((c: any) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
cycleService
|
||||
.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 = () => {
|
||||
if (!workspaceSlug || !projectId || !cycle) return;
|
||||
|
||||
mutate<ICycle[]>(
|
||||
CURRENT_CYCLE_LIST(projectId as string),
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((c) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
mutate(
|
||||
CYCLES_LIST(projectId as string),
|
||||
(prevData: any) =>
|
||||
(prevData ?? []).map((c: any) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
cycleService.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id).catch(() => {
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the cycle from favorites. Please try again.",
|
||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -296,8 +259,7 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
{cycle.is_favorite ? (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleRemoveFromFavorites();
|
||||
handleRemoveFromFavorites(e);
|
||||
}}
|
||||
>
|
||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||
@ -305,8 +267,7 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
) : (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleAddToFavorites();
|
||||
handleAddToFavorites(e);
|
||||
}}
|
||||
>
|
||||
<StarIcon className="h-4 w-4 " color="rgb(var(--color-text-200))" />
|
||||
@ -412,7 +373,7 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
<div className="my-3 flex max-h-[240px] min-h-[240px] flex-col gap-2.5 overflow-y-scroll rounded-md">
|
||||
{issues ? (
|
||||
issues.length > 0 ? (
|
||||
issues.map((issue) => (
|
||||
issues.map((issue: any) => (
|
||||
<div
|
||||
key={issue.id}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)}
|
||||
@ -480,14 +441,15 @@ export const ActiveCycleDetails: React.FC = () => {
|
||||
width:
|
||||
issues &&
|
||||
`${
|
||||
(issues.filter((issue) => issue?.state_detail?.group === "completed")?.length / issues.length) *
|
||||
(issues.filter((issue: any) => issue?.state_detail?.group === "completed")?.length /
|
||||
issues.length) *
|
||||
100 ?? 0
|
||||
}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-16 text-end text-xs text-custom-text-200">
|
||||
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of {issues?.length}
|
||||
{issues?.filter((issue: any) => issue?.state_detail?.group === "completed")?.length} of {issues?.length}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
134
web/components/cycles/cycle-create-edit-modal.tsx
Normal file
134
web/components/cycles/cycle-create-edit-modal.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { CycleForm } from "./form";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// types
|
||||
import { CycleDateCheckData, ICycle } from "types";
|
||||
|
||||
interface ICycleCreateEdit {
|
||||
cycle?: ICycle | null;
|
||||
modal: boolean;
|
||||
modalClose: () => void;
|
||||
onSubmit?: () => void;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CycleCreateEditModal: React.FC<ICycleCreateEdit> = observer((props) => {
|
||||
const { modal, modalClose, cycle = null, onSubmit, workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const validateCycleDate = async (payload: CycleDateCheckData) => {
|
||||
let status = false;
|
||||
await cycleStore.validateDate(workspaceSlug as string, projectId as string, payload).then((res) => {
|
||||
status = res.status;
|
||||
});
|
||||
return status;
|
||||
};
|
||||
|
||||
const formSubmit = async (data: Partial<ICycle>) => {
|
||||
let isDateValid: boolean = true;
|
||||
|
||||
if (data?.start_date && data?.end_date) {
|
||||
if (cycle?.id && cycle?.start_date && cycle?.end_date)
|
||||
isDateValid = await validateCycleDate({
|
||||
start_date: data.start_date,
|
||||
end_date: data.end_date,
|
||||
cycle_id: cycle.id,
|
||||
});
|
||||
else
|
||||
isDateValid = await validateCycleDate({
|
||||
start_date: data.start_date,
|
||||
end_date: data.end_date,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDateValid)
|
||||
if (cycle) {
|
||||
try {
|
||||
await cycleStore.updateCycle(workspaceSlug, projectId, cycle.id, data);
|
||||
if (modalClose) modalClose();
|
||||
if (onSubmit) onSubmit();
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle updated successfully.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Warning!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await cycleStore.createCycle(workspaceSlug, projectId, data);
|
||||
if (modalClose) modalClose();
|
||||
if (onSubmit) onSubmit();
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle created successfully.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Warning!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "You already have a cycle on the given dates, if you want to create a draft cycle, remove the dates.",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={modal} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={modalClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl p-5">
|
||||
<CycleForm handleFormSubmit={formSubmit} handleClose={modalClose} data={cycle} />
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
});
|
121
web/components/cycles/cycle-delete-modal.tsx
Normal file
121
web/components/cycles/cycle-delete-modal.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
// components
|
||||
import { DangerButton, SecondaryButton } from "components/ui";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
interface ICycleDelete {
|
||||
cycle: ICycle;
|
||||
modal: boolean;
|
||||
modalClose: () => void;
|
||||
onSubmit?: () => void;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
const { modal, modalClose, cycle, onSubmit, workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const [loader, setLoader] = useState(false);
|
||||
const formSubmit = async () => {
|
||||
setLoader(true);
|
||||
|
||||
if (cycle?.id)
|
||||
try {
|
||||
await cycleStore.removeCycle(workspaceSlug, projectId, cycle?.id);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
});
|
||||
if (modalClose) modalClose();
|
||||
if (onSubmit) onSubmit();
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Warning!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
}
|
||||
else
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Warning!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
|
||||
setLoader(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Transition.Root show={modal} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={modalClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
<div className="flex w-full items-center justify-start gap-4">
|
||||
<div className="flex-shrink-0 flex justify-center items-center rounded-full bg-red-500/20 w-12 h-12">
|
||||
<AlertTriangle width={16} strokeWidth={2} className="text-red-600" />
|
||||
</div>
|
||||
<div className="text-xl font-medium 2xl:text-2xl">Delete Cycle</div>
|
||||
</div>
|
||||
<span>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete cycle{' "'}
|
||||
<span className="break-words font-medium text-custom-text-100">{cycle?.name}</span>
|
||||
{'"'}? All of the data related to the cycle will be permanently removed. This action cannot be
|
||||
undone.
|
||||
</p>
|
||||
</span>
|
||||
<div className="flex justify-end gap-2">
|
||||
<SecondaryButton onClick={modalClose}>Cancel</SecondaryButton>
|
||||
<DangerButton onClick={formSubmit} loading={loader}>
|
||||
{loader ? "Deleting..." : "Delete Cycle"}
|
||||
</DangerButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,11 +1,14 @@
|
||||
import React, { FC } from "react";
|
||||
import { FC, MouseEvent, useState } from "react";
|
||||
// next imports
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
// headless ui
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { SingleProgressStats } from "components/core";
|
||||
import { CycleCreateEditModal } from "./cycle-create-edit-modal";
|
||||
import { CycleDeleteModal } from "./cycle-delete-modal";
|
||||
// ui
|
||||
import { CustomMenu } from "components/ui";
|
||||
import { AssigneesList } from "components/ui/avatar";
|
||||
@ -26,6 +29,8 @@ import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft
|
||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const stateGroups = [
|
||||
{
|
||||
@ -56,15 +61,23 @@ const stateGroups = [
|
||||
];
|
||||
|
||||
export interface ICyclesBoardCard {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycle: ICycle;
|
||||
filter: string;
|
||||
}
|
||||
|
||||
export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
const { cycle } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { cycle, workspaceSlug, projectId } = props;
|
||||
|
||||
const [updateModal, setUpdateModal] = useState(false);
|
||||
const updateModalCallback = () => {};
|
||||
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const deleteModalCallback = () => {};
|
||||
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -100,14 +113,52 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
cancelled: cycle.cancelled_issues,
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = () => {};
|
||||
const handleAddToFavorites = () => {};
|
||||
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const handleEditCycle = () => {};
|
||||
const handleDeleteCycle = () => {};
|
||||
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CycleCreateEditModal
|
||||
cycle={cycle}
|
||||
modal={updateModal}
|
||||
modalClose={() => setUpdateModal(false)}
|
||||
onSubmit={updateModalCallback}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<CycleDeleteModal
|
||||
cycle={cycle}
|
||||
modal={deleteModal}
|
||||
modalClose={() => setDeleteModal(false)}
|
||||
onSubmit={deleteModalCallback}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col rounded-[10px] bg-custom-background-100 border border-custom-border-200 text-xs shadow">
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="w-full">
|
||||
@ -241,7 +292,10 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
<div className="flex items-center">
|
||||
{!isCompleted && (
|
||||
<button
|
||||
onClick={handleEditCycle}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setUpdateModal(true);
|
||||
}}
|
||||
className="cursor-pointer rounded p-1 text-custom-text-200 duration-300 hover:bg-custom-background-80"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
@ -250,7 +304,12 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
|
||||
<CustomMenu width="auto" verticalEllipsis>
|
||||
{!isCompleted && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteCycle}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDeleteModal(true);
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete cycle</span>
|
||||
|
@ -7,17 +7,19 @@ import { CyclesBoardCard } from "components/cycles";
|
||||
export interface ICyclesBoard {
|
||||
cycles: ICycle[];
|
||||
filter: string;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = (props) => {
|
||||
const { cycles, filter } = props;
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-9 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{cycles.length > 0 ? (
|
||||
<>
|
||||
{cycles.map((cycle) => (
|
||||
<CyclesBoardCard key={cycle.id} cycle={cycle} filter={filter} />
|
||||
<CyclesBoardCard key={cycle.id} workspaceSlug={workspaceSlug} projectId={projectId} cycle={cycle} />
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { FC, MouseEvent } from "react";
|
||||
import { FC, MouseEvent, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { CycleCreateEditModal } from "./cycle-create-edit-modal";
|
||||
import { CycleDeleteModal } from "./cycle-delete-modal";
|
||||
// ui
|
||||
import { RadialProgressBar, Tooltip, LinearProgressIndicator } from "@plane/ui";
|
||||
import { CustomMenu } from "components/ui";
|
||||
@ -16,13 +18,14 @@ import {
|
||||
TriangleExclamationIcon,
|
||||
AlarmClockIcon,
|
||||
} from "components/icons";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
// helpers
|
||||
import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type TCyclesListItem = {
|
||||
cycle: ICycle;
|
||||
@ -30,6 +33,8 @@ type TCyclesListItem = {
|
||||
handleDeleteCycle?: () => void;
|
||||
handleAddToFavorites?: () => void;
|
||||
handleRemoveFromFavorites?: () => void;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
const stateGroups = [
|
||||
@ -61,12 +66,17 @@ const stateGroups = [
|
||||
];
|
||||
|
||||
export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
const { cycle } = props;
|
||||
const { cycle, workspaceSlug, projectId } = props;
|
||||
|
||||
const [updateModal, setUpdateModal] = useState(false);
|
||||
const updateModalCallback = () => {};
|
||||
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const deleteModalCallback = () => {};
|
||||
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -120,24 +130,18 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleDeleteCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col text-xs hover:bg-custom-background-80">
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="w-full">
|
||||
<div className="flex h-full flex-col gap-4 rounded-b-[10px] p-4">
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<div className="flex items-start gap-2">
|
||||
<>
|
||||
<div className="relative flex items-center gap-1 hover:bg-custom-background-80 transition-all rounded px-2 pl-3">
|
||||
<div className="w-full text-xs py-3">
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="w-full h-full relative overflow-hidden flex items-center gap-2">
|
||||
{/* left content */}
|
||||
<div className="relative flex items-center gap-2 overflow-hidden">
|
||||
{/* cycle state */}
|
||||
<div className="flex-shrink-0">
|
||||
<ContrastIcon
|
||||
className="mt-1 h-5 w-5"
|
||||
className="h-5 w-5"
|
||||
color={`${
|
||||
cycleStatus === "current"
|
||||
? "#09A953"
|
||||
@ -150,98 +154,114 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
<div className="max-w-2xl">
|
||||
<Tooltip tooltipContent={cycle.name} className="break-words" position="top-left">
|
||||
<h3 className="break-words w-full text-base font-semibold">{truncateText(cycle.name, 60)}</h3>
|
||||
</Tooltip>
|
||||
<p className="mt-2 text-custom-text-200 break-words w-full">{cycle.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex items-center gap-4">
|
||||
<span
|
||||
className={`rounded-full px-1.5 py-0.5
|
||||
|
||||
{/* cycle title and description */}
|
||||
<div className="max-w-xl">
|
||||
<Tooltip tooltipContent={cycle.name} className="break-words" position="top-left">
|
||||
<div className="text-base font-semibold line-clamp-1 pr-5 overflow-hidden break-words">
|
||||
{cycle.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{cycle.description && (
|
||||
<div className="mt-1 text-custom-text-200 break-words w-full line-clamp-2">{cycle.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* right content */}
|
||||
<div className="ml-auto flex-shrink-0 relative flex items-center gap-3 p-2">
|
||||
{/* cycle status */}
|
||||
<div
|
||||
className={`rounded-full px-2 py-1
|
||||
${
|
||||
cycleStatus === "current"
|
||||
? "bg-green-600/5 text-green-600"
|
||||
? "bg-green-600/10 text-green-600"
|
||||
: cycleStatus === "upcoming"
|
||||
? "bg-orange-300/5 text-orange-300"
|
||||
? "bg-orange-300/10 text-orange-300"
|
||||
: cycleStatus === "completed"
|
||||
? "bg-blue-500/5 text-blue-500"
|
||||
? "bg-blue-500/10 text-blue-500"
|
||||
: cycleStatus === "draft"
|
||||
? "bg-neutral-400/5 text-neutral-400"
|
||||
? "bg-neutral-400/10 text-neutral-400"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
<span className="flex gap-1 whitespace-nowrap">
|
||||
<PersonRunningIcon className="h-4 w-4" />
|
||||
{findHowManyDaysLeft(cycle.end_date ?? new Date())} days left
|
||||
</span>
|
||||
) : cycleStatus === "upcoming" ? (
|
||||
<span className="flex gap-1">
|
||||
<AlarmClockIcon className="h-4 w-4" />
|
||||
{findHowManyDaysLeft(cycle.start_date ?? new Date())} days left
|
||||
</span>
|
||||
) : cycleStatus === "completed" ? (
|
||||
<span className="flex items-center gap-1">
|
||||
{cycle.total_issues - cycle.completed_issues > 0 && (
|
||||
<Tooltip
|
||||
tooltipContent={`${cycle.total_issues - cycle.completed_issues} more pending ${
|
||||
cycle.total_issues - cycle.completed_issues === 1 ? "issue" : "issues"
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
<TriangleExclamationIcon className="h-3.5 w-3.5 fill-current" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}{" "}
|
||||
Completed
|
||||
</span>
|
||||
) : (
|
||||
cycleStatus
|
||||
)}
|
||||
</span>
|
||||
|
||||
{cycleStatus !== "draft" && (
|
||||
<div className="flex items-center justify-start gap-2 text-custom-text-200">
|
||||
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
<span>{renderShortDateWithYearFormat(startDate)}</span>
|
||||
</div>
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||
<TargetIcon className="h-4 w-4" />
|
||||
<span>{renderShortDateWithYearFormat(endDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
<span className="flex items-center gap-1 whitespace-nowrap">
|
||||
<PersonRunningIcon className="h-3.5 w-3.5" />
|
||||
{findHowManyDaysLeft(cycle.end_date ?? new Date())} days left
|
||||
</span>
|
||||
) : cycleStatus === "upcoming" ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<AlarmClockIcon className="h-3.5 w-3.5" />
|
||||
{findHowManyDaysLeft(cycle.start_date ?? new Date())} days left
|
||||
</span>
|
||||
) : cycleStatus === "completed" ? (
|
||||
<span className="flex items-center gap-1">
|
||||
{cycle.total_issues - cycle.completed_issues > 0 && (
|
||||
<Tooltip
|
||||
tooltipContent={`${cycle.total_issues - cycle.completed_issues} more pending ${
|
||||
cycle.total_issues - cycle.completed_issues === 1 ? "issue" : "issues"
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
<TriangleExclamationIcon className="h-3.5 w-3.5 fill-current" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}{" "}
|
||||
Completed
|
||||
</span>
|
||||
) : (
|
||||
cycleStatus
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 text-custom-text-200">
|
||||
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? (
|
||||
<img
|
||||
src={cycle.owned_by.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
className="rounded-full"
|
||||
alt={cycle.owned_by.display_name}
|
||||
/>
|
||||
) : (
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-orange-300 capitalize text-white">
|
||||
{cycle.owned_by.display_name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
{/* cycle start_date and target_date */}
|
||||
{cycleStatus !== "draft" && (
|
||||
<div className="flex items-center justify-start gap-2 text-custom-text-200">
|
||||
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
<span>{renderShortDateWithYearFormat(startDate)}</span>
|
||||
</div>
|
||||
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
|
||||
<div className="flex items-start gap-1 whitespace-nowrap">
|
||||
<TargetIcon className="h-4 w-4" />
|
||||
<span>{renderShortDateWithYearFormat(endDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
position="top-right"
|
||||
tooltipContent={
|
||||
<div className="flex w-80 items-center gap-2 px-4 py-1">
|
||||
<span>Progress</span>
|
||||
<LinearProgressIndicator data={progressIndicatorData} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`rounded-md px-1.5 py-1
|
||||
)}
|
||||
|
||||
{/* cycle created by */}
|
||||
<div className="flex items-center text-custom-text-200">
|
||||
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? (
|
||||
<img
|
||||
src={cycle.owned_by.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
className="rounded-full"
|
||||
alt={cycle.owned_by.display_name}
|
||||
/>
|
||||
) : (
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-orange-300 capitalize text-white">
|
||||
{cycle.owned_by.display_name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* cycle progress */}
|
||||
<Tooltip
|
||||
position="top-right"
|
||||
tooltipContent={
|
||||
<div className="flex w-80 items-center gap-2 px-4 py-1">
|
||||
<span>Progress</span>
|
||||
<LinearProgressIndicator data={progressIndicatorData} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`rounded-md px-1.5 py-1
|
||||
${
|
||||
cycleStatus === "current"
|
||||
? "border border-green-600 bg-green-600/5 text-green-600"
|
||||
@ -253,76 +273,98 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
? "border border-neutral-400 bg-neutral-400/5 text-neutral-400"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
<span className="flex gap-1 whitespace-nowrap">
|
||||
{cycle.total_issues > 0 ? (
|
||||
<>
|
||||
<RadialProgressBar progress={(cycle.completed_issues / cycle.total_issues) * 100} />
|
||||
<span>{Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} %</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="normal-case">No issues present</span>
|
||||
)}
|
||||
</span>
|
||||
) : cycleStatus === "upcoming" ? (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={100} /> Yet to start
|
||||
</span>
|
||||
) : cycleStatus === "completed" ? (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={100} />
|
||||
<span>{100} %</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={(cycle.total_issues / cycle.completed_issues) * 100} />
|
||||
{cycleStatus}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{cycle.is_favorite ? (
|
||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={handleAddToFavorites}>
|
||||
<StarIcon className="h-4 w-4 " color="rgb(var(--color-text-200))" />
|
||||
</button>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<CustomMenu width="auto" verticalEllipsis>
|
||||
{!isCompleted && (
|
||||
<CustomMenu.MenuItem onClick={handleEditCycle}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span>Edit Cycle</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
{!isCompleted && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteCycle}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete cycle</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy cycle link</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
<span className="flex gap-1 whitespace-nowrap">
|
||||
{cycle.total_issues > 0 ? (
|
||||
<>
|
||||
<RadialProgressBar progress={(cycle.completed_issues / cycle.total_issues) * 100} />
|
||||
<span>{Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} %</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="normal-case">No issues present</span>
|
||||
)}
|
||||
</span>
|
||||
) : cycleStatus === "upcoming" ? (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={100} /> Yet to start
|
||||
</span>
|
||||
) : cycleStatus === "completed" ? (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={100} />
|
||||
<span>{100} %</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex gap-1">
|
||||
<RadialProgressBar progress={(cycle.total_issues / cycle.completed_issues) * 100} />
|
||||
{cycleStatus}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{/* cycle favorite */}
|
||||
{cycle.is_favorite ? (
|
||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={handleAddToFavorites}>
|
||||
<StarIcon className="h-4 w-4 " color="rgb(var(--color-text-200))" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<CustomMenu width="auto" verticalEllipsis>
|
||||
{!isCompleted && (
|
||||
<CustomMenu.MenuItem onClick={() => setUpdateModal(true)}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span>Edit Cycle</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
{!isCompleted && (
|
||||
<CustomMenu.MenuItem onClick={() => setDeleteModal(true)}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<span>Delete cycle</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
<span>Copy cycle link</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CycleCreateEditModal
|
||||
cycle={cycle}
|
||||
modal={updateModal}
|
||||
modalClose={() => setUpdateModal(false)}
|
||||
onSubmit={updateModalCallback}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<CycleDeleteModal
|
||||
cycle={cycle}
|
||||
modal={deleteModal}
|
||||
modalClose={() => setDeleteModal(false)}
|
||||
onSubmit={deleteModalCallback}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import { CyclesListItem } from "./cycles-list-item";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
import { CyclesListItem } from "./cycles-list-item";
|
||||
|
||||
export interface ICyclesList {
|
||||
cycles: ICycle[];
|
||||
filter: string;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CyclesList: FC<ICyclesList> = (props) => {
|
||||
const { cycles, filter } = props;
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -22,7 +25,7 @@ export const CyclesList: FC<ICyclesList> = (props) => {
|
||||
{cycles.map((cycle) => (
|
||||
<div className="hover:bg-custom-background-80" key={cycle.id}>
|
||||
<div className="flex flex-col border-custom-border-200">
|
||||
<CyclesListItem cycle={cycle} />
|
||||
<CyclesListItem cycle={cycle} workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -1,255 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { KeyedMutator, mutate } from "swr";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
import {
|
||||
CreateUpdateCycleModal,
|
||||
CyclesListGanttChartView,
|
||||
DeleteCycleModal,
|
||||
SingleCycleCard,
|
||||
SingleCycleList,
|
||||
} from "components/cycles";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// helpers
|
||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// fetch-keys
|
||||
import {
|
||||
COMPLETED_CYCLES_LIST,
|
||||
CURRENT_CYCLE_LIST,
|
||||
CYCLES_LIST,
|
||||
DRAFT_CYCLES_LIST,
|
||||
UPCOMING_CYCLES_LIST,
|
||||
} from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
cycles: ICycle[] | undefined;
|
||||
mutateCycles?: KeyedMutator<ICycle[]>;
|
||||
viewType: string | null;
|
||||
};
|
||||
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const CyclesView: React.FC<Props> = ({ cycles, mutateCycles, 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 { storedValue: cycleTab } = useLocalStorage("cycle_tab", "all");
|
||||
console.log("cycleTab", cycleTab);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { user } = useUserAuth();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleEditCycle = (cycle: ICycle) => {
|
||||
setSelectedCycleToUpdate(cycle);
|
||||
setCreateUpdateCycleModal(true);
|
||||
};
|
||||
|
||||
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"
|
||||
? CURRENT_CYCLE_LIST(projectId as string)
|
||||
: cycleStatus === "upcoming"
|
||||
? UPCOMING_CYCLES_LIST(projectId as string)
|
||||
: cycleStatus === "completed"
|
||||
? COMPLETED_CYCLES_LIST(projectId as string)
|
||||
: DRAFT_CYCLES_LIST(projectId as string);
|
||||
|
||||
mutate<ICycle[]>(
|
||||
fetchKey,
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((c) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
mutate(
|
||||
CYCLES_LIST(projectId as string),
|
||||
(prevData: any) =>
|
||||
(prevData ?? []).map((c: any) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? true : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
cycleService
|
||||
.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"
|
||||
? CURRENT_CYCLE_LIST(projectId as string)
|
||||
: cycleStatus === "upcoming"
|
||||
? UPCOMING_CYCLES_LIST(projectId as string)
|
||||
: cycleStatus === "completed"
|
||||
? COMPLETED_CYCLES_LIST(projectId as string)
|
||||
: DRAFT_CYCLES_LIST(projectId as string);
|
||||
|
||||
mutate<ICycle[]>(
|
||||
fetchKey,
|
||||
(prevData) =>
|
||||
(prevData ?? []).map((c) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
mutate(
|
||||
CYCLES_LIST(projectId as string),
|
||||
(prevData: any) =>
|
||||
(prevData ?? []).map((c: any) => ({
|
||||
...c,
|
||||
is_favorite: c.id === cycle.id ? false : c.is_favorite,
|
||||
})),
|
||||
false
|
||||
);
|
||||
|
||||
cycleService.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.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateUpdateCycleModal
|
||||
isOpen={createUpdateCycleModal}
|
||||
handleClose={() => setCreateUpdateCycleModal(false)}
|
||||
data={selectedCycleToUpdate}
|
||||
user={user}
|
||||
/>
|
||||
<DeleteCycleModal
|
||||
isOpen={deleteCycleModal}
|
||||
setIsOpen={setDeleteCycleModal}
|
||||
data={selectedCycleToDelete}
|
||||
user={user}
|
||||
/>
|
||||
{cycles ? (
|
||||
cycles.length > 0 ? (
|
||||
viewType === "list" ? (
|
||||
<div className="divide-y divide-custom-border-200">
|
||||
{cycles.map((cycle) => (
|
||||
<div className="hover:bg-custom-background-80">
|
||||
<div className="flex flex-col border-custom-border-200">
|
||||
<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 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{cycles.map((cycle) => (
|
||||
<SingleCycleCard
|
||||
key={cycle.id}
|
||||
cycle={cycle}
|
||||
handleDeleteCycle={() => handleDeleteCycle(cycle)}
|
||||
handleEditCycle={() => handleEditCycle(cycle)}
|
||||
handleAddToFavorites={() => handleAddToFavorites(cycle)}
|
||||
handleRemoveFromFavorites={() => handleRemoveFromFavorites(cycle)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<CyclesListGanttChartView cycles={cycles ?? []} mutateCycles={mutateCycles} />
|
||||
)
|
||||
) : (
|
||||
<div className="h-full grid place-items-center text-center">
|
||||
<div className="space-y-2">
|
||||
<div className="mx-auto flex justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66" fill="none">
|
||||
<circle cx="34.375" cy="34.375" r="22" stroke="rgb(var(--color-text-400))" strokeLinecap="round" />
|
||||
<path
|
||||
d="M36.4375 20.9919C36.4375 19.2528 37.6796 17.8127 39.1709 18.1419C40.125 18.3526 41.0604 18.6735 41.9625 19.1014C43.7141 19.9322 45.3057 21.1499 46.6464 22.685C47.987 24.2202 49.0505 26.0426 49.776 28.0484C50.5016 30.0541 50.875 32.2038 50.875 34.3748C50.875 36.5458 50.5016 38.6956 49.776 40.7013C49.0505 42.7071 47.987 44.5295 46.6464 46.0647C45.3057 47.5998 43.7141 48.8175 41.9625 49.6483C41.0604 50.0762 40.125 50.3971 39.1709 50.6077C37.6796 50.937 36.4375 49.4969 36.4375 47.7578L36.4375 20.9919Z"
|
||||
fill="rgb(var(--color-text-400))"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 className="text-sm text-custom-text-200">
|
||||
{cycleTab === "all" ? "No cycles" : `No ${cycleTab} cycles`}
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
className="text-custom-primary-100 text-sm outline-none"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -4,35 +4,39 @@ import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CyclesBoard, CyclesList } from "components/cycles";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { CyclesBoard, CyclesList, CyclesListGanttChartView } from "components/cycles";
|
||||
// ui components
|
||||
import { Loader } from "components/ui";
|
||||
// types
|
||||
import { TCycleLayout } from "types";
|
||||
|
||||
export interface ICyclesView {
|
||||
filter: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete";
|
||||
view: "list" | "board" | "gantt";
|
||||
layout: TCycleLayout;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const CyclesView: FC<ICyclesView> = observer((props) => {
|
||||
const { filter, view, workspaceSlug, projectId } = props;
|
||||
const { filter, layout, workspaceSlug, projectId } = props;
|
||||
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
// api call to fetch cycles list
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `CYCLES_LIST_${projectId}_${filter}` : null,
|
||||
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, filter) : null
|
||||
workspaceSlug && projectId && filter ? `CYCLES_LIST_${projectId}_${filter}` : null,
|
||||
workspaceSlug && projectId && filter ? () => cycleStore.fetchCycles(workspaceSlug, projectId, filter) : null
|
||||
);
|
||||
|
||||
const cyclesList = cycleStore.cycles?.[projectId];
|
||||
console.log("cyclesList", cyclesList);
|
||||
|
||||
return (
|
||||
<>
|
||||
{view === "list" && (
|
||||
{layout === "list" && (
|
||||
<>
|
||||
{!isLoading ? (
|
||||
<CyclesList cycles={cyclesList} filter={filter} />
|
||||
<CyclesList cycles={cyclesList} filter={filter} workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
) : (
|
||||
<Loader className="space-y-4">
|
||||
<Loader.Item height="50px" />
|
||||
@ -42,10 +46,11 @@ export const CyclesView: FC<ICyclesView> = observer((props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{view === "board" && (
|
||||
|
||||
{layout === "board" && (
|
||||
<>
|
||||
{!isLoading ? (
|
||||
<CyclesBoard cycles={cyclesList} filter={filter} />
|
||||
<CyclesBoard cycles={cyclesList} filter={filter} workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
) : (
|
||||
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Loader.Item height="200px" />
|
||||
@ -55,7 +60,20 @@ export const CyclesView: FC<ICyclesView> = observer((props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{view === "gantt" && <CyclesList cycles={cyclesList} filter={filter} />}
|
||||
|
||||
{layout === "gantt" && (
|
||||
<>
|
||||
{!isLoading ? (
|
||||
<CyclesListGanttChartView cycles={cyclesList} workspaceSlug={workspaceSlug} />
|
||||
) : (
|
||||
<Loader className="space-y-4">
|
||||
<Loader.Item height="50px" />
|
||||
<Loader.Item height="50px" />
|
||||
<Loader.Item height="50px" />
|
||||
</Loader>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,176 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
// next
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
import { mutate } from "swr";
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// icons
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import type { IUser, ICycle, IProject } from "types";
|
||||
type TConfirmCycleDeletionProps = {
|
||||
isOpen: boolean;
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
data?: ICycle | null;
|
||||
user: IUser | undefined;
|
||||
};
|
||||
// fetch-keys
|
||||
import {
|
||||
COMPLETED_CYCLES_LIST,
|
||||
CURRENT_CYCLE_LIST,
|
||||
CYCLES_LIST,
|
||||
DRAFT_CYCLES_LIST,
|
||||
PROJECT_DETAILS,
|
||||
UPCOMING_CYCLES_LIST,
|
||||
} from "constants/fetch-keys";
|
||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
|
||||
// services
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({ isOpen, setIsOpen, data, user }) => {
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
setIsDeleteLoading(false);
|
||||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
if (!data || !workspaceSlug || !projectId) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await cycleService
|
||||
.deleteCycle(workspaceSlug as string, data.project, data.id, user)
|
||||
.then(() => {
|
||||
const cycleType = getDateRangeStatus(data.start_date, data.end_date);
|
||||
const fetchKey =
|
||||
cycleType === "current"
|
||||
? CURRENT_CYCLE_LIST(projectId as string)
|
||||
: cycleType === "upcoming"
|
||||
? UPCOMING_CYCLES_LIST(projectId as string)
|
||||
: cycleType === "completed"
|
||||
? COMPLETED_CYCLES_LIST(projectId as string)
|
||||
: DRAFT_CYCLES_LIST(projectId as string);
|
||||
|
||||
mutate<ICycle[]>(
|
||||
fetchKey,
|
||||
(prevData) => {
|
||||
if (!prevData) return;
|
||||
|
||||
return prevData.filter((cycle) => cycle.id !== data?.id);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
mutate(
|
||||
CYCLES_LIST(projectId as string),
|
||||
(prevData: any) => {
|
||||
if (!prevData) return;
|
||||
return prevData.filter((cycle: any) => cycle.id !== data?.id);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// update total cycles count in the project details
|
||||
mutate<IProject>(
|
||||
PROJECT_DETAILS(projectId.toString()),
|
||||
(prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return {
|
||||
...prevData,
|
||||
total_cycles: prevData.total_cycles - 1,
|
||||
};
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
handleClose();
|
||||
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Cycle deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setIsDeleteLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
|
||||
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||
Delete Cycle
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete cycle-{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the
|
||||
data related to the cycle will be permanently removed. This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 p-4 sm:px-6">
|
||||
<Button variant="neutral-primary" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={handleDeletion} loading={isDeleteLoading}>
|
||||
{isDeleteLoading ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
@ -16,6 +16,7 @@ import { CycleGanttBlock, CycleGanttSidebarBlock } from "components/cycles";
|
||||
import { ICycle } from "types";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
cycles: ICycle[];
|
||||
mutateCycles?: KeyedMutator<ICycle[]>;
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ export * from "./active-cycle-details";
|
||||
export * from "./active-cycle-stats";
|
||||
export * from "./gantt-chart";
|
||||
export * from "./cycles-view";
|
||||
export * from "./delete-cycle-modal";
|
||||
export * from "./form";
|
||||
export * from "./modal";
|
||||
export * from "./select";
|
||||
|
@ -11,7 +11,7 @@ import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { SidebarProgressStats } from "components/core";
|
||||
import ProgressChart from "components/core/sidebar/progress-chart";
|
||||
import { DeleteCycleModal } from "components/cycles";
|
||||
import { CycleDeleteModal } from "components/cycles/cycle-delete-modal";
|
||||
// ui
|
||||
import { CustomMenu, CustomRangeDatePicker } from "components/ui";
|
||||
import { Loader, ProgressBar } from "@plane/ui";
|
||||
@ -49,7 +49,11 @@ export const CycleDetailsSidebar: React.FC<Props> = ({ cycle, isOpen, cycleStatu
|
||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -261,7 +265,16 @@ export const CycleDetailsSidebar: React.FC<Props> = ({ cycle, isOpen, cycleStatu
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteCycleModal isOpen={cycleDeleteModal} setIsOpen={setCycleDeleteModal} data={cycle} user={user} />
|
||||
{cycle && (
|
||||
<CycleDeleteModal
|
||||
cycle={cycle}
|
||||
modal={cycleDeleteModal}
|
||||
modalClose={() => setCycleDeleteModal(false)}
|
||||
onSubmit={() => {}}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`fixed top-[66px] z-20 ${
|
||||
isOpen ? "right-0" : "-right-[24rem]"
|
||||
|
21
web/lib/local-storage.ts
Normal file
21
web/lib/local-storage.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export const getLocalStorage = (key: string) => {
|
||||
if (typeof window === undefined || typeof window === "undefined") return null;
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? item : null;
|
||||
} catch (error) {
|
||||
window.localStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const setLocalStorage = (key: string, value: any) => {
|
||||
if (key && value) {
|
||||
const _value = value ? (["string", "boolean"].includes(typeof value) ? value : JSON.stringify(value)) : null;
|
||||
if (_value) window.localStorage.setItem(key, _value);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeLocalStorage = (key: string) => {
|
||||
if (key) window.localStorage.removeItem(key);
|
||||
};
|
@ -1,25 +1,22 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import useSWR from "swr";
|
||||
// hooks
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import useUserAuth from "hooks/use-user-auth";
|
||||
import { Plus } from "lucide-react";
|
||||
// layouts
|
||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
|
||||
// components
|
||||
import { CyclesView, ActiveCycleDetails, CreateUpdateCycleModal } from "components/cycles";
|
||||
import { CyclesView, ActiveCycleDetails } from "components/cycles";
|
||||
import { CycleCreateEditModal } from "components/cycles/cycle-create-edit-modal";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
import { EmptyState } from "components/common";
|
||||
import { Icon } from "components/ui";
|
||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||
// icons
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// images
|
||||
import emptyCycle from "public/empty-state/cycle.svg";
|
||||
// types
|
||||
import { SelectCycleType } from "types";
|
||||
import { TCycleView, TCycleLayout } from "types";
|
||||
import type { NextPage } from "next";
|
||||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
@ -27,52 +24,66 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// constants
|
||||
import { CYCLE_TAB_LIST, CYCLE_VIEWS } from "constants/cycle";
|
||||
|
||||
type ICycleAPIFilter = "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete";
|
||||
type ICycleView = "list" | "board" | "gantt";
|
||||
// lib cookie
|
||||
import { setLocalStorage, getLocalStorage } from "lib/local-storage";
|
||||
|
||||
const ProjectCyclesPage: NextPage = observer(() => {
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
const createOnSubmit = () => {};
|
||||
|
||||
// store
|
||||
const { project: projectStore, cycle: cycleStore } = useMobxStore();
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
|
||||
// states
|
||||
const [selectedCycle, setSelectedCycle] = useState<SelectCycleType>();
|
||||
const [createUpdateCycleModal, setCreateUpdateCycleModal] = useState(false);
|
||||
// local storage
|
||||
const { storedValue: cycleTab, setValue: setCycleTab } = useLocalStorage("cycle_tab", "all");
|
||||
const { storedValue: cyclesView, setValue: setCyclesView } = useLocalStorage("cycle_view", "list");
|
||||
// hooks
|
||||
const { user } = useUserAuth();
|
||||
// api call fetch project details
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
||||
workspaceSlug && projectId
|
||||
? () => {
|
||||
projectStore.fetchProjectDetails(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
: null
|
||||
workspaceSlug && projectId ? () => projectStore.fetchProjectDetails(workspaceSlug, projectId) : null
|
||||
);
|
||||
|
||||
/**
|
||||
* Clearing form data after closing the modal
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (createUpdateCycleModal) return;
|
||||
const handleCurrentLayout = useCallback(
|
||||
(_layout: TCycleLayout) => {
|
||||
if (projectId) {
|
||||
setLocalStorage(`cycle_layout:${projectId}`, _layout);
|
||||
cycleStore.setCycleLayout(_layout);
|
||||
}
|
||||
},
|
||||
[cycleStore, projectId]
|
||||
);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setSelectedCycle(undefined);
|
||||
clearTimeout(timer);
|
||||
}, 500);
|
||||
}, [createUpdateCycleModal]);
|
||||
const handleCurrentView = useCallback(
|
||||
(_view: TCycleView) => {
|
||||
if (projectId) {
|
||||
setLocalStorage(`cycle_view:${projectId}`, _view);
|
||||
cycleStore.setCycleView(_view);
|
||||
if (_view === "draft" && cycleStore.cycleLayout === "gantt") {
|
||||
handleCurrentLayout("list");
|
||||
}
|
||||
}
|
||||
},
|
||||
[cycleStore, projectId, handleCurrentLayout]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (cycleTab === "draft" && cyclesView === "gantt") {
|
||||
setCyclesView("list");
|
||||
if (projectId) {
|
||||
const _viewKey = `cycle_view:${projectId}`;
|
||||
const _viewValue = getLocalStorage(_viewKey);
|
||||
if (_viewValue && _viewValue !== cycleStore?.cycleView) cycleStore.setCycleView(_viewValue as TCycleView);
|
||||
else handleCurrentView("all");
|
||||
|
||||
const _layoutKey = `cycle_layout:${projectId}`;
|
||||
const _layoutValue = getLocalStorage(_layoutKey);
|
||||
if (_layoutValue && _layoutValue !== cycleStore?.cycleView)
|
||||
cycleStore.setCycleLayout(_layoutValue as TCycleLayout);
|
||||
else handleCurrentLayout("list");
|
||||
}
|
||||
}, [cycleTab, cyclesView, setCyclesView]);
|
||||
}, [projectId, cycleStore, handleCurrentView, handleCurrentLayout]);
|
||||
|
||||
const projectDetails = projectId ? projectStore.project_details[projectId] : null;
|
||||
const cycleView = cycleStore?.cycleView;
|
||||
const cycleLayout = cycleStore?.cycleLayout;
|
||||
|
||||
return (
|
||||
<ProjectAuthorizationWrapper
|
||||
@ -85,22 +96,23 @@ const ProjectCyclesPage: NextPage = observer(() => {
|
||||
right={
|
||||
<Button
|
||||
variant="primary"
|
||||
prependIcon={<PlusIcon />}
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "q" });
|
||||
document.dispatchEvent(e);
|
||||
setCreateModal(true);
|
||||
}}
|
||||
>
|
||||
Add Cycle
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<CreateUpdateCycleModal
|
||||
isOpen={createUpdateCycleModal}
|
||||
handleClose={() => setCreateUpdateCycleModal(false)}
|
||||
data={selectedCycle}
|
||||
user={user}
|
||||
<CycleCreateEditModal
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
modal={createModal}
|
||||
modalClose={() => setCreateModal(false)}
|
||||
onSubmit={createOnSubmit}
|
||||
/>
|
||||
|
||||
{projectDetails?.total_cycles === 0 ? (
|
||||
<div className="h-full grid place-items-center">
|
||||
<EmptyState
|
||||
@ -108,13 +120,10 @@ const ProjectCyclesPage: NextPage = observer(() => {
|
||||
description="Cycle is a custom time period in which a team works to complete items on their backlog."
|
||||
image={emptyCycle}
|
||||
primaryButton={{
|
||||
icon: <PlusIcon className="h-4 w-4" />,
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Cycle",
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "q",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
setCreateModal(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -123,14 +132,10 @@ const ProjectCyclesPage: NextPage = observer(() => {
|
||||
<Tab.Group
|
||||
as="div"
|
||||
className="h-full flex flex-col overflow-hidden"
|
||||
defaultIndex={CYCLE_TAB_LIST.findIndex((i) => i.key === cycleTab)}
|
||||
selectedIndex={CYCLE_TAB_LIST.findIndex((i) => i.key === cycleTab)}
|
||||
defaultIndex={CYCLE_TAB_LIST.findIndex((i) => i.key == cycleStore?.cycleView)}
|
||||
selectedIndex={CYCLE_TAB_LIST.findIndex((i) => i.key == cycleStore?.cycleView)}
|
||||
onChange={(i) => {
|
||||
try {
|
||||
setCycleTab(CYCLE_TAB_LIST[i].key);
|
||||
} catch (e) {
|
||||
setCycleTab(CYCLE_TAB_LIST[0].key);
|
||||
}
|
||||
handleCurrentView(CYCLE_TAB_LIST[i].key as TCycleView);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-between border-b border-custom-border-300 px-4 sm:px-5 pb-4 sm:pb-0">
|
||||
@ -148,67 +153,74 @@ const ProjectCyclesPage: NextPage = observer(() => {
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<div className="justify-end sm:justify-start flex items-center gap-x-1">
|
||||
{CYCLE_VIEWS.map((view) => {
|
||||
if (cycleTab === "active") return null;
|
||||
if (view.key === "gantt" && cycleTab === "draft") return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={view.key}
|
||||
type="button"
|
||||
className={`grid h-8 w-8 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-background-80 ${
|
||||
cyclesView === view.key ? "bg-custom-background-80 text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
onClick={() => setCyclesView(view.key)}
|
||||
>
|
||||
<Icon iconName={view.icon} className="!text-base" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{CYCLE_VIEWS && CYCLE_VIEWS.length > 0 && cycleStore?.cycleView != "active" && (
|
||||
<div className="justify-end sm:justify-start flex items-center gap-x-1">
|
||||
{CYCLE_VIEWS.map((view) => {
|
||||
if (view.key === "gantt" && cycleStore?.cycleView === "draft") return null;
|
||||
return (
|
||||
<button
|
||||
key={view.key}
|
||||
type="button"
|
||||
className={`grid h-8 w-8 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-background-80 ${
|
||||
cycleStore?.cycleLayout === view.key
|
||||
? "bg-custom-background-80 text-custom-text-100"
|
||||
: "text-custom-text-200"
|
||||
}`}
|
||||
onClick={() => handleCurrentLayout(view.key as TCycleLayout)}
|
||||
>
|
||||
<Icon iconName={view.icon} className="!text-base" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Tab.Panels as={React.Fragment}>
|
||||
|
||||
<Tab.Panels as={Fragment}>
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 h-full overflow-y-auto">
|
||||
{cycleTab && cyclesView && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
<CyclesView
|
||||
filter="all"
|
||||
view={cyclesView as ICycleView}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
filter={"all"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 space-y-5 h-full overflow-y-auto">
|
||||
<ActiveCycleDetails />
|
||||
<ActiveCycleDetails workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 h-full overflow-y-auto">
|
||||
{cycleTab && cyclesView && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
<CyclesView
|
||||
filter="upcoming"
|
||||
view={cyclesView as ICycleView}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
filter={"upcoming"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 h-full overflow-y-auto">
|
||||
{cycleTab && cyclesView && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
<CyclesView
|
||||
filter="completed"
|
||||
view={cyclesView as ICycleView}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
filter={"completed"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
||||
<Tab.Panel as="div" className="p-4 sm:p-5 h-full overflow-y-auto">
|
||||
{cycleTab && cyclesView && workspaceSlug && projectId && (
|
||||
{cycleView && cycleLayout && workspaceSlug && projectId && (
|
||||
<CyclesView
|
||||
filter="draft"
|
||||
view={cyclesView as ICycleView}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
filter={"draft"}
|
||||
layout={cycleLayout as TCycleLayout}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
||||
// types
|
||||
import { ICycle, TCycleView, TCycleLayout, CycleDateCheckData, IIssue } from "types";
|
||||
// mobx
|
||||
import { RootStore } from "../root";
|
||||
import { ICycle } from "types";
|
||||
// services
|
||||
import { ProjectService } from "services/project";
|
||||
import { IssueService } from "services/issue";
|
||||
@ -11,6 +12,9 @@ export interface ICycleStore {
|
||||
loader: boolean;
|
||||
error: any | null;
|
||||
|
||||
cycleView: TCycleView;
|
||||
cycleLayout: TCycleLayout;
|
||||
|
||||
cycleId: string | null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
@ -18,21 +22,31 @@ export interface ICycleStore {
|
||||
cycle_details: {
|
||||
[cycle_id: string]: ICycle;
|
||||
};
|
||||
active_cycle_issues: {
|
||||
[cycle_id: string]: IIssue[];
|
||||
};
|
||||
|
||||
// computed
|
||||
getCycleById: (cycleId: string) => ICycle | null;
|
||||
|
||||
// actions
|
||||
setCycleView: (_cycleView: TCycleView) => void;
|
||||
setCycleLayout: (_cycleLayout: TCycleLayout) => void;
|
||||
setCycleId: (cycleId: string) => void;
|
||||
|
||||
validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise<any>;
|
||||
|
||||
fetchCycles: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete"
|
||||
) => Promise<void>;
|
||||
fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
|
||||
fetchActiveCycleIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
|
||||
createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise<ICycle>;
|
||||
updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise<ICycle>;
|
||||
removeCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
|
||||
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
|
||||
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||
@ -42,6 +56,9 @@ export class CycleStore implements ICycleStore {
|
||||
loader: boolean = false;
|
||||
error: any | null = null;
|
||||
|
||||
cycleView: TCycleView = "all";
|
||||
cycleLayout: TCycleLayout = "list";
|
||||
|
||||
cycleId: string | null = null;
|
||||
cycles: {
|
||||
[project_id: string]: ICycle[];
|
||||
@ -51,6 +68,10 @@ export class CycleStore implements ICycleStore {
|
||||
[cycle_id: string]: ICycle;
|
||||
} = {};
|
||||
|
||||
active_cycle_issues: {
|
||||
[cycle_id: string]: IIssue[];
|
||||
} = {};
|
||||
|
||||
// root store
|
||||
rootStore;
|
||||
// services
|
||||
@ -63,20 +84,31 @@ export class CycleStore implements ICycleStore {
|
||||
loader: observable,
|
||||
error: observable.ref,
|
||||
|
||||
cycleView: observable,
|
||||
cycleLayout: observable,
|
||||
|
||||
cycleId: observable,
|
||||
cycles: observable.ref,
|
||||
cycle_details: observable.ref,
|
||||
active_cycle_issues: observable.ref,
|
||||
|
||||
// computed
|
||||
projectCycles: computed,
|
||||
|
||||
// actions
|
||||
setCycleView: action,
|
||||
setCycleLayout: action,
|
||||
setCycleId: action,
|
||||
getCycleById: action,
|
||||
|
||||
fetchCycles: action,
|
||||
fetchCycleWithId: action,
|
||||
|
||||
fetchActiveCycleIssues: action,
|
||||
|
||||
createCycle: action,
|
||||
updateCycle: action,
|
||||
removeCycle: action,
|
||||
|
||||
addCycleToFavorites: action,
|
||||
removeCycleFromFavorites: action,
|
||||
@ -97,8 +129,18 @@ export class CycleStore implements ICycleStore {
|
||||
getCycleById = (cycleId: string) => this.cycle_details[cycleId] || null;
|
||||
|
||||
// actions
|
||||
setCycleId = (cycleId: string) => {
|
||||
this.cycleId = cycleId;
|
||||
setCycleView = (_cycleView: TCycleView) => (this.cycleView = _cycleView);
|
||||
setCycleLayout = (_cycleLayout: TCycleLayout) => (this.cycleLayout = _cycleLayout);
|
||||
setCycleId = (cycleId: string) => (this.cycleId = cycleId);
|
||||
|
||||
validateDate = async (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => {
|
||||
try {
|
||||
const response = await this.cycleService.cycleDateCheck(workspaceSlug, projectId, payload);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to validate cycle dates", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
fetchCycles = async (
|
||||
@ -112,6 +154,8 @@ export class CycleStore implements ICycleStore {
|
||||
|
||||
const cyclesResponse = await this.cycleService.getCyclesWithParams(workspaceSlug, projectId, params);
|
||||
|
||||
if (this.cycleView === "active") this.fetchActiveCycleIssues(workspaceSlug, projectId, cyclesResponse[0].id);
|
||||
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
@ -144,6 +188,27 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
const _cycleIssues = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, {
|
||||
priority: `urgent,high`,
|
||||
});
|
||||
|
||||
const _activeCycleIssues = {
|
||||
...this.active_cycle_issues,
|
||||
[cycleId]: _cycleIssues as IIssue[],
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.active_cycle_issues = _activeCycleIssues;
|
||||
});
|
||||
|
||||
return _activeCycleIssues;
|
||||
} catch (error) {
|
||||
console.log("error");
|
||||
}
|
||||
};
|
||||
|
||||
createCycle = async (workspaceSlug: string, projectId: string, data: any) => {
|
||||
try {
|
||||
const response = await this.cycleService.createCycle(
|
||||
@ -154,12 +219,15 @@ export class CycleStore implements ICycleStore {
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: [...this.cycles[projectId], response],
|
||||
this.cycle_details = {
|
||||
...this.cycle_details,
|
||||
[response?.id]: response,
|
||||
};
|
||||
});
|
||||
|
||||
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
|
||||
this.fetchCycles(workspaceSlug, projectId, _currentView);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to create cycle from cycle store");
|
||||
@ -171,23 +239,18 @@ export class CycleStore implements ICycleStore {
|
||||
try {
|
||||
const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
||||
|
||||
const _cycles = {
|
||||
...this.cycles,
|
||||
[projectId]: this.cycles[projectId].map((cycle) => {
|
||||
if (cycle.id === cycleId) return { ...cycle, ...response };
|
||||
return cycle;
|
||||
}),
|
||||
};
|
||||
const _cycleDetails = {
|
||||
...this.cycle_details,
|
||||
[cycleId]: { ...this.cycle_details[cycleId], ...response },
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.cycles = _cycles;
|
||||
this.cycle_details = _cycleDetails;
|
||||
});
|
||||
|
||||
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
|
||||
this.fetchCycles(workspaceSlug, projectId, _currentView);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to update cycle from cycle store");
|
||||
@ -195,6 +258,43 @@ export class CycleStore implements ICycleStore {
|
||||
}
|
||||
};
|
||||
|
||||
patchCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: any) => {
|
||||
try {
|
||||
const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data, undefined);
|
||||
|
||||
const _cycleDetails = {
|
||||
...this.cycle_details,
|
||||
[cycleId]: { ...this.cycle_details[cycleId], ..._response },
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
this.cycle_details = _cycleDetails;
|
||||
});
|
||||
|
||||
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
|
||||
this.fetchCycles(workspaceSlug, projectId, _currentView);
|
||||
|
||||
return _response;
|
||||
} catch (error) {
|
||||
console.log("Failed to patch cycle from cycle store");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
removeCycle = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
const _response = await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId, undefined);
|
||||
|
||||
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
|
||||
this.fetchCycles(workspaceSlug, projectId, _currentView);
|
||||
|
||||
return _response;
|
||||
} catch (error) {
|
||||
console.log("Failed to delete cycle from cycle store");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||
try {
|
||||
runInAction(() => {
|
||||
@ -237,12 +337,10 @@ export class CycleStore implements ICycleStore {
|
||||
}),
|
||||
};
|
||||
});
|
||||
// updating through api
|
||||
const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to remove cycle from favorites - Cycle Store", error);
|
||||
// resetting the local state
|
||||
runInAction(() => {
|
||||
this.cycles = {
|
||||
...this.cycles,
|
||||
|
8
web/types/cycles.d.ts
vendored
8
web/types/cycles.d.ts
vendored
@ -9,6 +9,10 @@ import type {
|
||||
IUserLite,
|
||||
} from "types";
|
||||
|
||||
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
||||
|
||||
export type TCycleLayout = "list" | "board" | "gantt";
|
||||
|
||||
export interface ICycle {
|
||||
backlog_issues: number;
|
||||
cancelled_issues: number;
|
||||
@ -82,9 +86,7 @@ export interface CycleIssueResponse {
|
||||
sub_issues_count: number;
|
||||
}
|
||||
|
||||
export type SelectCycleType =
|
||||
| (ICycle & { actionType: "edit" | "delete" | "create-issue" })
|
||||
| undefined;
|
||||
export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
||||
|
||||
export type SelectIssue = (IIssue & { actionType: "edit" | "delete" | "create" }) | null;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user