Compare commits

...

63 Commits

Author SHA1 Message Date
PalanikannanM
2ca8448350 Merge branch 'develop' into fix/table-colors-row-col-add 2024-01-22 11:47:23 +00:00
Palanikannan1437
1e40f75770 removed uncessary/conflicting css for tables 2024-01-19 11:22:26 +05:30
Palanikannan1437
27fc28b7dc feat: added ability to toggle row and column header (not turned on by default) 2024-01-19 11:17:30 +05:30
Palanikannan1437
0082eff4a0 regression: reverted link checks of malicious scripts 2024-01-19 11:01:30 +05:30
Palanikannan1437
3557bc024b feat: Added consistent row colors in tables 2024-01-18 17:34:46 +05:30
Palanikannan1437
7c86fbc554 Merge branch 'develop' into fix/table-colors-row-col-add 2024-01-18 16:34:44 +05:30
sriram veeraghanta
57c25c9a5a Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-18 16:05:31 +05:30
rahulramesha
c593d5df1b fix: enable global/ all issues (#3405)
* fix global issues and views

* remove separate layouts for specific views

* add permissions to views

* fix global issues filters

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-18 15:51:17 +05:30
Bavisetti Narayan
9065b5d368 feat: dashboard widgets (#3362)
* fix: created dashboard, widgets and dashboard widget model

* fix: new user home dashboard

* chore: recent projects list

* chore: recent collaborators

* chore: priority order change

* chore: payload changes

* chore: collaborator's active issue count

* chore: all dashboard widgets added with services and typs

* chore: centered metric for pie chart

* chore: widget filters

* chore: created issue filter

* fix: created and assigned issues payload change

* chore: created issue payload change

* fix: date filter change

* chore: implement filters

* fix: added expansion fields

* fix: changed issue structure with relation

* chore: new issues response

* fix: project member fix

* chore: updated issue_relation structure

* chore: code cleanup

* chore: update issues response and added empty states

* fix: button text wrap

* chore: update empty state messages

* fix: filters

* chore: update dark mode empty states

* build-error: Type check in the issue relation service

* fix: issues redirection

* fix: project empty state

* chore: project member active check

* chore: project member check in state and priority

* chore: remove console logs and replace harcoded values with constants

* fix: code refactoring

* fix: key name changed

* refactor: mapping through similar components using an array

* fix: build errors

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-18 15:49:54 +05:30
Prateek Shourya
a9e2e21641 refactor: update create/update issue modal to use currently active store's create/update method. (#3395)
* refactor: update `create/update issue` modal to use currently active store's create/update method.

* chore: add condition to avoid multiple API calls if the current store is MODULE or CYCLE.

* remove: console log

* chore: update `currentStore` to `storeType`.
2024-01-18 14:42:10 +05:30
sriram veeraghanta
e175d50ab7 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-18 13:47:16 +05:30
M. Palanikannan
75b8e3350a 🐛 fix: Hide drag handle when cursor leaves the editor container (#3401)
This fix adds support for hiding the drag handle when the cursor leaves the editor container. It improves the user experience by providing a cleaner interface and removing unnecessary visual elements especially while scrolling.

- Add `hideDragHandle` prop to `EditorContainer` component in `editor-container.tsx`.
- Implement `onMouseLeave` event handler in `EditorContainer` to invoke `hideDragHandle` function.
- Update `DragAndDrop` extension in `drag-drop.tsx` to accept a `setHideDragHandle` function as an optional parameter.
- Pass the `setHideDragHandle` function from `RichTextEditor` component to `DragAndDrop` extension in `RichTextEditorExtensions` function in `index.tsx`.
- Set `hideDragHandleOnMouseLeave` state in `RichTextEditor` component to store the `hideDragHandlerFromDragDrop` function.
- Create `setHideDragHandleFunction` callback function in `RichTextEditor` to update the `hideDragHandleOnMouseLeave` state.
- Pass `hideDragHandleOnMouseLeave` as `hideDragHandle` prop to `EditorContainer` component in `RichTextEditor`.
2024-01-18 12:43:43 +05:30
Prateek Shourya
6e1cd4194a fix: stack integration disable button mutation issue in project settings. (#3402) 2024-01-18 12:31:10 +05:30
Anmol Singh Bhatia
615ccf9459 chore: workspace active cycles improvement (#3396) 2024-01-17 23:04:53 +05:30
sriram veeraghanta
13362590b6 fix: resolved merge conflicts while moving changes from preview to develop 2024-01-17 19:13:47 +05:30
Prateek Shourya
7833ca7bea fix: project views bugs related to store refactor. (#3391)
* chore: remove debounce logic to fix create/ update view modal bugs.

* fix: bug in delete views not mutating the store.

* chore: replace `Project Empty State` with `Project Views Empty State`.

* chore: add issue peek overview.

* refactor: issue update, delete actions for project views layout.
fix: issue update and delete action throwing error bug.
fix: issue quick add throwing error bug.
2024-01-17 18:37:46 +05:30
M. Palanikannan
a1d27a1bf0 [chore]: Removed explicit dependencies and cleaned up turbo config (#3388)
* Removed explicit dependencies and cleaned up turbo config

* fix: upgrade turbo

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-17 17:24:56 +05:30
rahulramesha
8fbd4a059b fix: refactor related bugs (#3384)
* fix sub issues inside issue detail

* close peek over view after opening issue detail

* fix error while opening peek overview

* fix saving project views

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-16 21:16:12 +05:30
Anmol Singh Bhatia
e751686683 chore: filter and display properties improvement (#3382) 2024-01-16 20:57:55 +05:30
Anmol Singh Bhatia
8ee5ba96ce dev: workspace active cycles (#3378)
* chore: workspace active cycles

* fix: active cycles tab implementation

* chore: added distribution graph for active cycles

* chore: removed distribution graph and issues

* Revert "chore: removed issues"

This reverts commit 7d977ac8b0.

* chore: workspace active cycles implementation

* chore: code refactor

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-16 19:54:32 +05:30
Anmol Singh Bhatia
9e8885df5f chore: esc to close peek overview added (#3380) 2024-01-16 18:23:42 +05:30
Anmol Singh Bhatia
bc48010377 fix: drag and delete issue (#3379) 2024-01-16 18:22:53 +05:30
Lakhan Baheti
9fde539b1d chore webhook create page removed (#3376)
* chore webhook create page removed

* fix: removed unused variables
2024-01-16 14:22:48 +05:30
guru_sainath
ec26bf6e68 chore: update in sub-issues component and property validation and issue loaders (#3375)
* fix: handled undefined issue_id in list layout

* chore: refactor peek overview and user role validation.

* chore: sub issues

* fix: sub issues state distribution changed

* chore: sub_issues implementation in issue detail page

* chore: fixes in cycle/ module layout.
* Fix progress chart
* Module issues's update/ delete.
* Peek Overview for Modules/ Cycle.
* Fix Cycle Filters not applying bug.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-16 12:46:03 +05:30
sriram veeraghanta
e9ef3fb32a chore: formatting all python files using black formatter (#3366) 2024-01-13 19:05:06 +05:30
rahulramesha
ee2c7c5fa1 enable peekoverview for spreadsheet and minor refactor for faster opening of the peekoverview component (#3361)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-12 13:52:04 +05:30
rahulramesha
d64ae9a2e4 fix: project loaders for mobx store (#3356)
* add loaders to all the dropdowns outside project wrpper

* fix build errors

* minor refactor for project states color

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-12 13:51:00 +05:30
Henit Chobisa
f58a00a4ab [FIX] Pages Malfunctioning on Load and Recent Pages Computation (#3359)
* fix: fixed `usePage` hook returning context instead of IPageStore

* fix: updated recent pages with `updated_at` instead of `created_at`

* fix: thown error instead of returning empty array
2024-01-12 13:26:48 +05:30
sriram veeraghanta
a3e5284f71 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-12 12:25:57 +05:30
sriram veeraghanta
1c06c3f43e fix: create more toggle fixes in create issue modal (#3355)
* fix: create more issue bugfixes

* fix: removing all warning
2024-01-11 21:01:05 +05:30
sriram veeraghanta
da1496fe65 fix: create sync action (#3353)
* fix: create sync action changes

* fix: typo changes
2024-01-11 18:40:26 +05:30
M. Palanikannan
3d489e186f fix: inline code blocks, code blocks and links have saner behaviour (#3318)
* fix: removed backticks in inline code blocks

* added better error handling while cancelling uploads

* fix: inline code blocks, code blocks and links have saner behaviour

- Inline code blocks are now exitable, don't have backticks, have better padding vertically and better regex matching
- Code blocks on the top and bottom of the document are now exitable via Up and Down Arrow keys
- Links are now exitable while being autolinkable via a custom re-write of the tiptap-link-extension

* fix: more robust link checking
2024-01-11 18:29:41 +05:30
guru_sainath
57d5ff7646 chore: Error Handling and Validation Updates (#3351)
* fix: handled undefined issue_id in list layout

* chore: updated label select dropdown in the issue detail

* fix: peekoverview issue is resolved

* chore: user role validation for issue details.

* fix: Link, Attachement, parent mutation

* build-error: build error resolved in peekoverview

* chore: user role validation for issue details.

* chore: user role validation for `issue description`, `parent`, `relation` and `subscription`.

* chore: issue subscription mutation

* chore: user role validation for `labels` in issue details.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2024-01-11 18:26:58 +05:30
rahulramesha
3c9926d383 update swr config to not fetch everything on focus (#3350)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-11 18:21:41 +05:30
rahulramesha
ece4d5b1ed chore: Refactor Spreadsheet view for better code maintainability and performance (#3322)
* refcator spreadsheet to use table and roow based approach rather than column based

* update spreadsheet and optimized layout

* fix issues in spread sheet

* close quick action menu on click

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-11 18:19:19 +05:30
guru_sainath
73eed69aa6 chore: refactored and resolved build issues on the issues and issue detail page (#3340)
* fix: handled undefined issue_id in list layout

* dev: issue detail store and optimization

* dev: issue filter and list operations

* fix: typo on labels update

* dev: Handled all issues in the list layout in project issues

* dev: handled kanban and auick add issue in swimlanes

* chore: fixed peekoverview in kanban

* chore: fixed peekoverview in calendar

* chore: fixed peekoverview in gantt

* chore: updated quick add in the gantt chart

* chore: handled issue detail properties and resolved build issues

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-10 20:09:45 +05:30
Henit Chobisa
09603cf189 fix: link preview editor (#3335)
* feat: added link preview plugin in document editor

* fix: readonly editor page renderer css

* fix: autolink issue with links

* chore: added floating UI

* feat: added link preview components

* feat: added floating UI to page renderer for link previews

* feat: added actionCompleteHandler to page renderer

* chore: Lock file changes

* fix: regex security error

* chore: updated radix with lucid icons

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-10 18:18:09 +05:30
Nikhil
23e53df3ad dev: fix smtp configuration (#3339) 2024-01-10 18:16:28 +05:30
Nikhil
57594aac4e dev: update the instance urls (#3329) 2024-01-10 12:22:20 +05:30
Anmol Singh Bhatia
8b884ab681 chore: modal and dropdown improvement (#3332)
* dev: dropdown key down custom hook added

* chore: plane ui dropdowns updated

* chore: cycle and module tab index added in modals

* chore: view and page tab index added in modals

* chore: issue modal tab indexing added

* chore: project modal tab indexing added

* fix: build fix

* build-error: build error in pages new structure and reverted back to old page structure

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-10 12:21:24 +05:30
sriram veeraghanta
08e5f2b156 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-09 22:51:51 +05:30
sriram veeraghanta
cb3a73e515 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-09 20:41:49 +05:30
Palanikannan1437
eef9edff24 table row colors consistent 2024-01-09 09:42:46 +05:30
sriram veeraghanta
cb2a7d0930 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-08 23:29:36 +05:30
sriram veeraghanta
c38e048ce8 Merge branches 'fix/pages-store' and 'develop' of github.com:makeplane/plane into develop 2024-01-08 23:28:12 +05:30
Bavisetti Narayan
94b72effbf chore: mobile configs (#3328)
* chore: mobile configs

* chore: mobile configurations changed

* chore: removed the slack id

* chore: reversed google client id
2024-01-08 23:25:14 +05:30
rahulramesha
eccb1f5d10 fix: breaking cycle issues and replacing router.push with Links (#3330)
* fix cycle creation and active cycle map

* minor fix in cycle store

* create cycle breaking fix

* replace last possible bits of router.push with Link

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-08 19:20:42 +05:30
Prateek Shourya
a71491ecb9 fix: estimate order not maintained in create/ update modal. (#3326)
* fix: estimate order not maintained in create/ update modal.

* fix: estimate points mutation on update.
2024-01-08 16:16:45 +05:30
sriram veeraghanta
455c2cc787 fix: pages store structure changes 2024-01-07 12:05:52 +05:30
Palanikannan1437
44dc602ac3 fix: removed repetitive css styles and removed header row by default
- fixed css
- removed header row to render by default
- added row and column header toggle options
2024-01-06 10:22:27 +05:30
Anmol Singh Bhatia
81f6557908 fix: workspace invitations response updated (#3321) 2024-01-05 23:42:52 +05:30
Anmol Singh Bhatia
2f10f35191 chore: bug fixes and improvement (#3303)
* refactor: updated preloaded function for the list view quick add

* fix: resolved bug in the assignee dropdown

* chore: issue sidebar link improvement

* fix: resolved subscription store bug

* chore: updated preloaded function for the kanban layout quick add

* chore: resolved issues in the list filters and component

* chore: filter store updated

* fix: issue serializer changed

* chore: quick add preload function updated

* fix: build error

* fix: serializer changed

* fix: minor request change

* chore: resolved build issues and updated the prepopulated data in the quick add issue.

* fix: build fix and code refactor

* fix: spreadsheet layout quick add fix

* fix: issue peek overview link section updated

* fix: cycle status bug fix

* fix: serializer changes

* fix: assignee and labels listing

* chore: issue modal parent_id default value updated

* fix: cycle and module issue serializer change

* fix: cycle list serializer changed

* chore: prepopulated validation in both list and kanban for quick add and group header add issues

* chore: group header validation added

* fix: issue response payload change

* dev: make cycle and module issue create response simillar

* chore: custom control link component added

* dev: make issue create and update response simillar to list and retrieve

* fix: build error

* chore: control link component improvement

* chore: globalise issue peek overview

* chore: control link component improvement

* chore: made changes and optimised the issue peek overview root

* build-error: resolved build erros for issueId dependancy from issue detail store

* chore: peek overview link fix

* dev: update state nullable rule

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-05 23:37:13 +05:30
Prateek Shourya
cf64c7bbc6 fix: project identifier cursor behaviour in create project modal. (#3320) 2024-01-05 14:38:09 +05:30
Prateek Shourya
9dd8c8ba14 chore: UI/UX improvements (#3319)
* chore: add proper message for cycle/ module having start & end date but isn't active yet.

* fix: infinite loader after updating workspace settings.

* fix: user profile icon dropdown doesn't closes automatically.

* style: fix inconsistent padding in cycle empty state.

* chore: remove multiple `empty state` in labels settings and improve add label logic.

* style: fix inconsistent padding in project label, integration and estimates empty state.

* style: fix integrations settings breadcrumb title.

* style: add proper `disabled` styles for email field in profile settings.

* style: fix cycle layout height.
2024-01-05 14:13:04 +05:30
Palanikannan1437
bb4bee00cb fix: inline code blocks, code blocks and links have saner behaviour
- Inline code blocks are now exitable, don't have backticks, have better padding vertically and better regex matching
- Code blocks on the top and bottom of the document are now exitable via Up and Down Arrow keys
- Links are now exitable while being autolinkable via a custom re-write of the tiptap-link-extension
2024-01-05 10:52:20 +05:30
Palanikannan1437
d8f1404462 added better error handling while cancelling uploads 2024-01-05 10:45:13 +05:30
Palanikannan1437
927ab50ac6 fix: removed backticks in inline code blocks 2024-01-05 10:43:47 +05:30
sriram veeraghanta
d98b688342 fix: merge conflicts resolved 2024-01-04 17:28:11 +05:30
sriram veeraghanta
ce21630388 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-04 17:27:22 +05:30
M. Palanikannan
0927fa150c chore: Updated TableView component in table extension to solve sentry (#3309)
error of table not being defined while getting getBoundingClientRect()
and solve other TS issues

- Added ResolvedPos import from @tiptap/pm/model
- Updated setCellsBackgroundColor function parameter type to string
- Declared ToolboxItem type for toolbox items
- Modified columnsToolboxItems and rowsToolboxItems to use the ToolboxItem type
- Updated createToolbox function parameters to specify Element or null for triggerButton and ToolboxItem[] for items
- Added ts-expect-error comment above the toolbox variable declaration
- Updated update method parameter type to readonly Decoration[]
- Changed destructuring assignment of hoveredTable and hoveredCell in updateControls method to use Object.values and reduce method
- Added null check for this.table in updateControls method
- Wrapped the code that updates columnsControl and rowsControl with null checks for each control
- Replaced ts-ignore comments with proper dispatch calls in selectColumn and selectRow methods

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-04 16:30:10 +05:30
Aaryan Khandelwal
eec411baaf dev: new create issue modal (#3312) 2024-01-04 16:29:18 +05:30
sriram veeraghanta
ecc8fbd79b fix: Login workflow depending on smtp is configured (#3307) 2024-01-04 16:27:17 +05:30
sriram veeraghanta
c9b628e578 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-04 16:25:27 +05:30
32 changed files with 791 additions and 142 deletions

View File

@@ -8,10 +8,16 @@ from plane.app.views import (
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
CycleUserPropertiesEndpoint,
ActiveCycleEndpoint
)
urlpatterns = [
path(
"workspaces/<str:slug>/active-cycles/",
ActiveCycleEndpoint.as_view(),
name="workspace-active-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/",
CycleViewSet.as_view(

View File

@@ -62,6 +62,7 @@ from .cycle import (
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
CycleUserPropertiesEndpoint,
ActiveCycleEndpoint,
)
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
from .issue import (

View File

@@ -909,3 +909,235 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
)
serializer = CycleUserPropertiesSerializer(cycle_properties)
return Response(serializer.data, status=status.HTTP_200_OK)
class ActiveCycleEndpoint(BaseAPIView):
permission_classes = [
WorkspaceUserPermission,
]
def get(self, request, slug):
subquery = CycleFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
active_cycles = (
Cycle.objects.filter(
workspace__slug=slug,
project__project_projectmember__member=self.request.user,
start_date__lte=timezone.now(),
end_date__gte=timezone.now(),
)
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.annotate(
total_issues=Count(
"issue_cycle",
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
completed_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
cancelled_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
started_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
unstarted_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
backlog_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(total_estimates=Sum("issue_cycle__issue__estimate_point"))
.annotate(
completed_estimates=Sum(
"issue_cycle__issue__estimate_point",
filter=Q(
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
started_estimates=Sum(
"issue_cycle__issue__estimate_point",
filter=Q(
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
status=Case(
When(
Q(start_date__lte=timezone.now())
& Q(end_date__gte=timezone.now()),
then=Value("CURRENT"),
),
When(start_date__gt=timezone.now(), then=Value("UPCOMING")),
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
When(
Q(start_date__isnull=True) & Q(end_date__isnull=True),
then=Value("DRAFT"),
),
default=Value("DRAFT"),
output_field=CharField(),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__assignees",
queryset=User.objects.only("avatar", "first_name", "id").distinct(),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__labels",
queryset=Label.objects.only("name", "color", "id").distinct(),
)
)
.order_by("-created_at")
)
cycles = CycleSerializer(active_cycles, many=True).data
for cycle in cycles:
assignee_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=cycle["id"],
project_id=cycle["project"],
workspace__slug=slug,
)
.annotate(display_name=F("assignees__display_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(avatar=F("assignees__avatar"))
.values("display_name", "assignee_id", "avatar")
.annotate(
total_issues=Count(
"assignee_id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("display_name")
)
label_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=cycle["id"],
project_id=cycle["project"],
workspace__slug=slug,
)
.annotate(label_name=F("labels__name"))
.annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
filter=Q(archived_at__isnull=True, is_draft=False),
)
)
.annotate(
completed_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("label_name")
)
cycle["distribution"] = {
"assignees": assignee_distribution,
"labels": label_distribution,
"completion_chart": {},
}
if cycle["start_date"] and cycle["end_date"]:
cycle["distribution"][
"completion_chart"
] = burndown_plot(
queryset=active_cycles.get(pk=cycle["id"]),
slug=slug,
project_id=cycle["project"],
cycle_id=cycle["id"],
)
return Response(cycles, status=status.HTTP_200_OK)

View File

@@ -73,8 +73,8 @@ export const insertTableCommand = (editor: Editor, range?: Range) => {
}
}
}
if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
else editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3 }).run();
else editor.chain().focus().insertTable({ rows: 3, cols: 3 }).run();
};
export const unsetLinkEditor = (editor: Editor) => {

View File

@@ -170,68 +170,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
}
}
#editor-container {
table {
border-collapse: collapse;
table-layout: fixed;
margin: 0.5em 0 0.5em 0;
border: 1px solid rgb(var(--color-border-200));
width: 100%;
td,
th {
min-width: 1em;
border: 1px solid rgb(var(--color-border-200));
padding: 10px 15px;
vertical-align: top;
box-sizing: border-box;
position: relative;
transition: background-color 0.3s ease;
> * {
margin-bottom: 0;
}
}
th {
font-weight: bold;
text-align: left;
background-color: rgb(var(--color-primary-100));
}
td:hover {
background-color: rgba(var(--color-primary-300), 0.1);
}
.selectedCell:after {
z-index: 2;
position: absolute;
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(var(--color-primary-300), 0.1);
pointer-events: none;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: -2px;
width: 2px;
background-color: rgb(var(--color-primary-400));
pointer-events: none;
}
}
}
.tableWrapper {
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;

View File

@@ -9,8 +9,8 @@
border-collapse: collapse;
table-layout: fixed;
margin: 0;
margin-bottom: 3rem;
border: 1px solid rgba(var(--color-border-200));
margin-bottom: 1rem;
border: 2px solid rgba(var(--color-border-200));
width: 100%;
}
@@ -118,9 +118,7 @@
background-size: 1.25rem;
background-repeat: no-repeat;
background-position: center;
transition:
transform ease-out 100ms,
background-color ease-out 100ms;
transition: transform ease-out 100ms, background-color ease-out 100ms;
outline: none;
box-shadow: #000 0px 2px 4px;
cursor: pointer;
@@ -133,13 +131,12 @@
background-size: 1.25rem;
background-repeat: no-repeat;
background-position: center;
transition:
transform ease-out 100ms,
background-color ease-out 100ms;
transition: transform ease-out 100ms, background-color ease-out 100ms;
outline: none;
box-shadow: #000 0px 2px 4px;
cursor: pointer;
}
.tableWrapper .tableControls .tableToolbox,
.tableWrapper .tableControls .tableColorPickerToolbox {
border: 1px solid rgba(var(--color-border-300));

View File

@@ -12,6 +12,7 @@ export const CustomQuoteExtension = Blockquote.extend({
if (parent.type.name !== "blockquote") {
return false;
}
if ($from.pos !== $to.pos) return false;
// if ($head.parentOffset < $head.parent.content.size) return false;

View File

@@ -13,7 +13,7 @@ export const TableCell = Node.create<TableCellOptions>({
};
},
content: "paragraph+",
content: "block+",
addAttributes() {
return {

View File

@@ -13,6 +13,14 @@ export const TableRow = Node.create<TableRowOptions>({
};
},
addAttributes() {
return {
backgroundColor: {
default: null,
},
};
},
content: "(tableCell | tableHeader)*",
tableRole: "row",
@@ -21,7 +29,17 @@ export const TableRow = Node.create<TableRowOptions>({
return [{ tag: "tr" }];
},
// renderHTML({ HTMLAttributes }) {
// return ["tr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
// },
renderHTML({ HTMLAttributes }) {
return ["tr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
// Check if backgroundColor attribute is set and create a style string accordingly
const style = HTMLAttributes.backgroundColor ? `background-color: ${HTMLAttributes.backgroundColor};` : "";
// Merge any existing HTMLAttributes with the style for backgroundColor
const attributes = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { style });
return ["tr", attributes, 0];
},
});

View File

@@ -2,6 +2,8 @@ export const icons = {
colorPicker: `<svg xmlns="http://www.w3.org/2000/svg" length="24" viewBox="0 0 24 24" style="transform: ;msFilter:;"><path fill="rgb(var(--color-text-300))" d="M20 14c-.092.064-2 2.083-2 3.5 0 1.494.949 2.448 2 2.5.906.044 2-.891 2-2.5 0-1.5-1.908-3.436-2-3.5zM9.586 20c.378.378.88.586 1.414.586s1.036-.208 1.414-.586l7-7-.707-.707L11 4.586 8.707 2.293 7.293 3.707 9.586 6 4 11.586c-.378.378-.586.88-.586 1.414s.208 1.036.586 1.414L9.586 20zM11 7.414 16.586 13H5.414L11 7.414z"></path></svg>`,
deleteColumn: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M12 3c.552 0 1 .448 1 1v8c.835-.628 1.874-1 3-1 2.761 0 5 2.239 5 5s-2.239 5-5 5c-1.032 0-1.99-.313-2.787-.848L13 20c0 .552-.448 1-1 1H6c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zm-1 2H7v14h4V5zm8 10h-6v2h6v-2z"/></svg>`,
deleteRow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M20 5c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1 .628.835 1 1.874 1 3 0 2.761-2.239 5-5 5s-5-2.239-5-5c0-1.126.372-2.165 1-3H4c-.552 0-1-.448-1-1V6c0-.552.448-1 1-1h16zm-7 10v2h6v-2h-6zm6-8H5v4h14V7z"/></svg>`,
toggleColumnHeader: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--color-text-300))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-toggle-right"><rect width="20" height="12" x="2" y="6" rx="6" ry="6"/><circle cx="16" cy="12" r="2"/></svg>`,
toggleRowHeader: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--color-text-300))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-toggle-right"><rect width="20" height="12" x="2" y="6" rx="6" ry="6"/><circle cx="16" cy="12" r="2"/></svg>`,
insertLeftTableIcon: `<svg
xmlns="http://www.w3.org/2000/svg"
length={24}

View File

@@ -1,5 +1,6 @@
import { h } from "jsx-dom-cjs";
import { Node as ProseMirrorNode, ResolvedPos } from "@tiptap/pm/model";
// import { setCellAttr } from "prosemirror-tables";
import { Decoration, NodeView } from "@tiptap/pm/view";
import tippy, { Instance, Props } from "tippy.js";
@@ -95,6 +96,11 @@ function setCellsBackgroundColor(editor: Editor, backgroundColor: string) {
}
const columnsToolboxItems: ToolboxItem[] = [
{
label: "Toggle Column Header",
icon: icons.toggleColumnHeader,
action: ({ editor }: { editor: Editor }) => editor.chain().focus().toggleHeaderColumn().run(),
},
{
label: "Add Column Before",
icon: icons.insertLeftTableIcon,
@@ -133,7 +139,46 @@ const columnsToolboxItems: ToolboxItem[] = [
},
];
function setTableRowBackgroundColor(editor: Editor, backgroundColor: string) {
const { state, dispatch } = editor.view;
const { selection } = state;
if (!(selection instanceof CellSelection)) {
return false;
}
// Get the position of the hovered cell in the selection to determine the row.
const hoveredCell = selection.$headCell || selection.$anchorCell;
// Find the depth of the table row node
let rowDepth = hoveredCell.depth;
while (rowDepth > 0 && hoveredCell.node(rowDepth).type.name !== "tableRow") {
rowDepth--;
}
// If we couldn't find a tableRow node, we can't set the background color
if (hoveredCell.node(rowDepth).type.name !== "tableRow") {
return false;
}
// Get the position where the table row starts
const rowStartPos = hoveredCell.start(rowDepth);
// Create a transaction that sets the background color on the tableRow node.
const tr = state.tr.setNodeMarkup(rowStartPos - 1, null, {
...hoveredCell.node(rowDepth).attrs,
backgroundColor: backgroundColor,
});
dispatch(tr);
return true;
}
const rowsToolboxItems: ToolboxItem[] = [
{
label: "Toggle Row Header",
icon: icons.toggleRowHeader,
action: ({ editor }: { editor: Editor }) => editor.chain().focus().toggleHeaderRow().run(),
},
{
label: "Add Row Above",
icon: icons.insertTopTableIcon,
@@ -161,7 +206,7 @@ const rowsToolboxItems: ToolboxItem[] = [
tippyOptions: {
appendTo: controlsContainer,
},
onSelectColor: (color) => setCellsBackgroundColor(editor, color),
onSelectColor: (color) => setTableRowBackgroundColor(editor, color),
});
},
},

View File

@@ -107,7 +107,7 @@ export const Table = Node.create({
addCommands() {
return {
insertTable:
({ rows = 3, cols = 3, withHeaderRow = true } = {}) =>
({ rows = 3, cols = 3, withHeaderRow = false } = {}) =>
({ tr, dispatch, editor }) => {
const node = createTable(editor.schema, rows, cols, withHeaderRow);

View File

@@ -42,15 +42,6 @@ export function CoreEditorProps(
return false;
},
handleDrop: (view, event, _slice, moved) => {
if (typeof window !== "undefined") {
const selection: any = window?.getSelection();
if (selection.rangeCount !== 0) {
const range = selection.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return;
}
}
}
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
event.preventDefault();
const file = event.dataTransfer.files[0];

View File

@@ -52,6 +52,23 @@ export const PageRenderer = (props: IPageRenderer) => {
const { getFloatingProps } = useInteractions([dismiss]);
const [linkViewProps, setLinkViewProps] = useState<LinkViewProps>();
const [isOpen, setIsOpen] = useState(false);
const [coordinates, setCoordinates] = useState<{ x: number; y: number }>();
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [flip(), shift(), hide({ strategy: "referenceHidden" })],
whileElementsMounted: autoUpdate,
});
const dismiss = useDismiss(context, {
ancestorScroll: true,
});
const { getFloatingProps } = useInteractions([dismiss]);
const handlePageTitleChange = (title: string) => {
setPagetitle(title);
updatePageTitle(title);

View File

@@ -48,34 +48,13 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
function getComplexItems(): BubbleMenuItem[] {
const items: BubbleMenuItem[] = [TableItem(editor)];
if (shouldShowImageItem()) {
items.push(ImageItem(editor, uploadFile, setIsSubmitting));
}
items.push(ImageItem(editor, uploadFile, setIsSubmitting));
return items;
}
const complexItems: BubbleMenuItem[] = getComplexItems();
function shouldShowImageItem(): boolean {
if (typeof window !== "undefined") {
const selectionRange: any = window?.getSelection();
const { selection } = props.editor.state;
if (selectionRange.rangeCount !== 0) {
const range = selectionRange.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return false;
}
if (isCellSelection(selection)) {
return false;
}
}
return true;
}
return false;
}
return (
<div className="flex items-center divide-x divide-custom-border-200">
<div className="flex items-center gap-0.5 pr-2">

View File

@@ -59,35 +59,12 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
function getComplexItems(): BubbleMenuItem[] {
const items: BubbleMenuItem[] = [TableItem(props.editor)];
if (shouldShowImageItem()) {
items.push(ImageItem(props.editor, props.uploadFile, props.setIsSubmitting));
}
items.push(ImageItem(props.editor, props.uploadFile, props.setIsSubmitting));
return items;
}
const complexItems: BubbleMenuItem[] = getComplexItems();
function shouldShowImageItem(): boolean {
if (typeof window !== "undefined") {
const selectionRange: any = window?.getSelection();
const { selection } = props.editor.state;
if (selectionRange.rangeCount !== 0) {
const range = selectionRange.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return false;
}
if (isCellSelection(selection)) {
return false;
}
}
return true;
}
return false;
}
const handleAccessChange = (accessKey: string) => {
props.commentAccessSpecifier?.onAccessChange(accessKey);
};

View File

@@ -1,4 +1,11 @@
import type { IUser, TIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "@plane/types";
import type {
IUser,
TIssue,
IProjectLite,
IWorkspaceLite,
IIssueFilterOptions,
IUserLite,
} from "@plane/types";
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
@@ -40,6 +47,7 @@ export interface ICycle {
};
workspace: string;
workspace_detail: IWorkspaceLite;
issues?: TIssue[];
}
export type TAssigneesDistribution = {
@@ -80,9 +88,13 @@ 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 = (TIssue & { actionType: "edit" | "delete" | "create" }) | null;
export type SelectIssue =
| (TIssue & { actionType: "edit" | "delete" | "create" })
| null;
export type CycleDateCheckData = {
start_date: string;

View File

@@ -1,5 +1,11 @@
import { EUserProjectRoles } from "constants/project";
import type { IUser, IUserLite, IWorkspace, IWorkspaceLite, TStateGroups } from ".";
import type {
IUser,
IUserLite,
IWorkspace,
IWorkspaceLite,
TStateGroups,
} from ".";
export interface IProject {
archive_in: number;
@@ -52,6 +58,11 @@ export interface IProjectLite {
id: string;
name: string;
identifier: string;
emoji: string | null;
icon_prop: {
name: string;
color: string;
} | null;
}
type ProjectPreferences = {

View File

@@ -6,6 +6,7 @@ import { observer } from "mobx-react-lite";
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import { useApplication } from "hooks/store";
// ui
import { Button, Input } from "@plane/ui";
// helpers

View File

@@ -22,11 +22,6 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) =
// derived values
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
const redirect = (path: string) => {
closePalette();
router.push(path);
};
return (
<>
{WORKSPACE_SETTINGS_LINKS.map(

View File

@@ -0,0 +1,254 @@
import { FC, MouseEvent, useCallback } from "react";
import Link from "next/link";
// ui
import {
AvatarGroup,
Tooltip,
LinearProgressIndicator,
ContrastIcon,
RunningIcon,
LayersIcon,
StateGroupIcon,
Avatar,
} from "@plane/ui";
// components
import { SingleProgressStats } from "components/core";
import { ActiveCycleProgressStats } from "./active-cycle-stats";
// hooks
import { useCycle } from "hooks/store";
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// icons
import { ArrowRight, CalendarDays, Star, Target } from "lucide-react";
// types
import { ICycle, TCycleLayout, TCycleView } from "@plane/types";
// helpers
import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper";
import { truncateText } from "helpers/string.helper";
// constants
import { STATE_GROUPS_DETAILS } from "constants/cycle";
export type ActiveCycleInfoProps = {
cycle: ICycle;
workspaceSlug: string;
projectId: string;
};
export const ActiveCycleInfo: FC<ActiveCycleInfoProps> = (props) => {
const { cycle, workspaceSlug, projectId } = props;
// store
const { addCycleToFavorites, removeCycleFromFavorites } = useCycle();
// local storage
const { setValue: setCycleTab } = useLocalStorage<TCycleView>("cycle_tab", "active");
const { setValue: setCycleLayout } = useLocalStorage<TCycleLayout>("cycle_layout", "list");
// toast alert
const { setToastAlert } = useToast();
const groupedIssues: any = {
backlog: cycle.backlog_issues,
unstarted: cycle.unstarted_issues,
started: cycle.started_issues,
completed: cycle.completed_issues,
cancelled: cycle.cancelled_issues,
};
const progressIndicatorData = STATE_GROUPS_DETAILS.map((group, index) => ({
id: index,
name: group.title,
value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0,
color: group.color,
}));
const handleCurrentLayout = useCallback(
(_layout: TCycleLayout) => {
setCycleLayout(_layout);
},
[setCycleLayout]
);
const handleCurrentView = useCallback(
(_view: TCycleView) => {
setCycleTab(_view);
if (_view === "draft") handleCurrentLayout("list");
},
[handleCurrentLayout, setCycleTab]
);
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
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;
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 className="grid-row-2 grid divide-y rounded-[10px] border border-custom-border-200 bg-custom-background-100 shadow">
<div className="grid grid-cols-1 divide-y border-custom-border-200 lg:grid-cols-3 lg:divide-x lg:divide-y-0">
<div className="flex flex-col text-xs">
<div className="h-full w-full">
<div className="flex h-60 flex-col justify-between gap-5 rounded-b-[10px] p-4">
<div className="flex items-center justify-between gap-1">
<span className="flex items-center gap-1">
<span className="h-5 w-5">
<ContrastIcon className="h-5 w-5" color="#09A953" />
</span>
<Tooltip tooltipContent={cycle.name} position="top-left">
<h3 className="break-words text-lg font-semibold">{truncateText(cycle.name, 70)}</h3>
</Tooltip>
</span>
<span className="flex items-center gap-1 capitalize">
<span className="rounded-full px-1.5 py-0.5 bg-green-600/5 text-green-600">
<span className="flex gap-1 whitespace-nowrap">
<RunningIcon className="h-4 w-4" />
{findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left
</span>
</span>
{cycle.is_favorite ? (
<button
onClick={(e) => {
handleRemoveFromFavorites(e);
}}
>
<Star className="h-4 w-4 fill-orange-400 text-orange-400" />
</button>
) : (
<button
onClick={(e) => {
handleAddToFavorites(e);
}}
>
<Star className="h-4 w-4 " color="rgb(var(--color-text-200))" />
</button>
)}
</span>
</div>
<div className="flex items-center justify-start gap-5 text-custom-text-200">
<div className="flex items-start gap-1">
<CalendarDays className="h-4 w-4" />
{cycle?.start_date && <span>{renderFormattedDate(cycle?.start_date)}</span>}
</div>
<ArrowRight className="h-4 w-4 text-custom-text-200" />
<div className="flex items-start gap-1">
<Target className="h-4 w-4" />
{cycle?.end_date && <span>{renderFormattedDate(cycle?.end_date)}</span>}
</div>
</div>
<div className="flex items-center gap-4">
<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-custom-background-100 capitalize">
{cycle.owned_by.display_name.charAt(0)}
</span>
)}
<span className="text-custom-text-200">{cycle.owned_by.display_name}</span>
</div>
{cycle.assignees.length > 0 && (
<div className="flex items-center gap-1 text-custom-text-200">
<AvatarGroup>
{cycle.assignees.map((assignee: any) => (
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} />
))}
</AvatarGroup>
</div>
)}
</div>
<div className="flex items-center gap-4 text-custom-text-200">
<div className="flex gap-2">
<LayersIcon className="h-4 w-4 flex-shrink-0" />
{cycle.total_issues} issues
</div>
<div className="flex items-center gap-2">
<StateGroupIcon stateGroup="completed" height="14px" width="14px" />
{cycle.completed_issues} issues
</div>
</div>
<div className="flex item-center gap-2">
<Link
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
onClick={() => {
handleCurrentView("active");
}}
>
<span className="w-full rounded-md bg-custom-primary px-4 py-2 text-center text-sm font-medium text-white hover:bg-custom-primary/90">
View Cycle
</span>
</Link>
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
<span className="w-full rounded-md bg-custom-primary px-4 py-2 text-center text-sm font-medium text-white hover:bg-custom-primary/90">
View Cycle Issues
</span>
</Link>
</div>
</div>
</div>
</div>
<div className="col-span-2 grid grid-cols-1 divide-y border-custom-border-200 md:grid-cols-2 md:divide-x md:divide-y-0">
<div className="flex h-60 flex-col border-custom-border-200">
<div className="flex h-full w-full flex-col p-4 text-custom-text-200">
<div className="flex w-full items-center gap-2 py-1">
<span>Progress</span>
<LinearProgressIndicator data={progressIndicatorData} />
</div>
<div className="mt-2 flex flex-col items-center gap-1">
{Object.keys(groupedIssues).map((group, index) => (
<SingleProgressStats
key={index}
title={
<div className="flex items-center gap-2">
<span
className="block h-3 w-3 rounded-full "
style={{
backgroundColor: STATE_GROUPS_DETAILS[index].color,
}}
/>
<span className="text-xs capitalize">{group}</span>
</div>
}
completed={groupedIssues[group]}
total={cycle.total_issues}
/>
))}
</div>
</div>
</div>
<div className="h-60 overflow-y-scroll border-custom-border-200">
<ActiveCycleProgressStats cycle={cycle} />
</div>
</div>
</div>
</div>
);
};

View File

@@ -15,3 +15,4 @@ export * from "./cycles-board-card";
export * from "./delete-modal";
export * from "./cycle-peek-overview";
export * from "./cycles-list-item";
export * from "./active-cycle-info";

View File

@@ -20,3 +20,4 @@ export * from "./project-archived-issue-details";
export * from "./project-archived-issues";
export * from "./project-issue-details";
export * from "./user-profile";
export * from "./workspace-active-cycle";

View File

@@ -0,0 +1,46 @@
import { observer } from "mobx-react-lite";
import { Search, SendToBack } from "lucide-react";
// hooks
import { useWorkspace } from "hooks/store";
// ui
import { Breadcrumbs } from "@plane/ui";
export const WorkspaceActiveCycleHeader = observer(() => {
// store hooks
const { workspaceActiveCyclesSearchQuery, setWorkspaceActiveCyclesSearchQuery } = useWorkspace();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<SendToBack className="h-4 w-4 text-custom-text-300" />}
label="Active Cycles"
/>
</Breadcrumbs>
<span
className="flex items-center justify-center px-3.5 py-0.5 text-xs leading-5 rounded-xl"
style={{
color: "#F59E0B",
backgroundColor: "#F59E0B20",
}}
>
Beta
</span>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex w-full items-center justify-start gap-1 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5 text-custom-text-400">
<Search className="h-3.5 w-3.5" />
<input
className="w-full min-w-[234px] border-none bg-transparent text-sm focus:outline-none"
value={workspaceActiveCyclesSearchQuery}
onChange={(e) => setWorkspaceActiveCyclesSearchQuery(e.target.value)}
placeholder="Search"
/>
</div>
</div>
</div>
);
});

View File

@@ -9,7 +9,7 @@ type Props = {
export const ViewIssueLabel: React.FC<Props> = ({ labelDetails, maxRender = 1 }) => (
<>
{labelDetails.length > 0 ? (
{labelDetails?.length > 0 ? (
labelDetails.length <= maxRender ? (
<>
{labelDetails.map((label) => (

View File

@@ -8,3 +8,4 @@ export * from "./send-workspace-invitation-modal";
export * from "./sidebar-dropdown";
export * from "./sidebar-menu";
export * from "./sidebar-quick-action";
export * from "./workspace-active-cycles-list";

View File

@@ -0,0 +1,57 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import useSWR from "swr";
// components
import { ActiveCycleInfo } from "components/cycles";
// services
import { CycleService } from "services/cycle.service";
const cycleService = new CycleService();
// hooks
import { useWorkspace } from "hooks/store";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
export const WorkspaceActiveCyclesList = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// fetching active cycles in workspace
const { data } = useSWR("WORKSPACE_ACTIVE_CYCLES", () => cycleService.workspaceActiveCycles(workspaceSlug as string));
// store
const { workspaceActiveCyclesSearchQuery } = useWorkspace();
// filter cycles based on search query
const filteredCycles = data?.filter(
(cycle) =>
cycle.project_detail.name.toLowerCase().includes(workspaceActiveCyclesSearchQuery.toLowerCase()) ||
cycle.project_detail.identifier?.toLowerCase().includes(workspaceActiveCyclesSearchQuery.toLowerCase()) ||
cycle.name.toLowerCase().includes(workspaceActiveCyclesSearchQuery.toLowerCase())
);
return (
<div>
{workspaceSlug &&
filteredCycles &&
filteredCycles.map((cycle) => (
<div key={cycle.id} className="px-5 py-7">
<div className="flex items-center gap-1.5 px-3 py-1.5">
{cycle.project_detail.emoji ? (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
{renderEmoji(cycle.project_detail.emoji)}
</span>
) : cycle.project_detail.icon_prop ? (
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
{renderEmoji(cycle.project_detail.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{cycle.project_detail?.name.charAt(0)}
</span>
)}
<h2 className="text-xl font-semibold">{cycle.project_detail.name}</h2>
</div>
<ActiveCycleInfo workspaceSlug={workspaceSlug?.toString()} projectId={cycle.project} cycle={cycle} />
</div>
))}
</div>
);
});

View File

@@ -86,3 +86,32 @@ export const CYCLE_STATUS: {
bgColor: "bg-custom-background-90",
},
];
export const STATE_GROUPS_DETAILS = [
{
key: "backlog_issues",
title: "Backlog",
color: "#dee2e6",
},
{
key: "unstarted_issues",
title: "Unstarted",
color: "#26b5ce",
},
{
key: "started_issues",
title: "Started",
color: "#f7ae59",
},
{
key: "cancelled_issues",
title: "Cancelled",
color: "#d687ff",
},
{
key: "completed_issues",
title: "Completed",
color: "#09a953",
},
];

View File

@@ -0,0 +1,16 @@
import { ReactElement } from "react";
// components
import { WorkspaceActiveCyclesList } from "components/workspace";
import { WorkspaceActiveCycleHeader } from "components/headers";
// layouts
import { AppLayout } from "layouts/app-layout";
// types
import { NextPageWithLayout } from "lib/types";
const WorkspaceActiveCyclesPage: NextPageWithLayout = () => <WorkspaceActiveCyclesList />;
WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) {
return <AppLayout header={<WorkspaceActiveCycleHeader />}>{page}</AppLayout>;
};
export default WorkspaceActiveCyclesPage;

View File

@@ -44,7 +44,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
const editorRef = useRef<any>(null);
// router
const router = useRouter();
const { workspaceSlug, projectId, pageId } = router.query;
// store hooks
const {

View File

@@ -10,6 +10,14 @@ export class CycleService extends APIService {
super(API_BASE_URL);
}
async workspaceActiveCycles(workspaceSlug: string): Promise<ICycle[]> {
return this.get(`/api/workspaces/${workspaceSlug}/active-cycles/`)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
async createCycle(workspaceSlug: string, projectId: string, data: any): Promise<ICycle> {
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data)
.then((response) => response?.data)

View File

@@ -12,9 +12,12 @@ import { ApiTokenStore, IApiTokenStore } from "./api-token.store";
export interface IWorkspaceRootStore {
// observables
workspaces: Record<string, IWorkspace>;
workspaceActiveCyclesSearchQuery: string;
// computed
currentWorkspace: IWorkspace | null;
workspacesCreatedByCurrentUser: IWorkspace[] | null;
// actions
setWorkspaceActiveCyclesSearchQuery: (query: string) => void;
// computed actions
getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null;
getWorkspaceById: (workspaceId: string) => IWorkspace | null;
@@ -31,6 +34,7 @@ export interface IWorkspaceRootStore {
export class WorkspaceRootStore implements IWorkspaceRootStore {
// observables
workspaceActiveCyclesSearchQuery: string = "";
workspaces: Record<string, IWorkspace> = {};
// services
workspaceService;
@@ -45,6 +49,7 @@ export class WorkspaceRootStore implements IWorkspaceRootStore {
makeObservable(this, {
// observables
workspaces: observable,
workspaceActiveCyclesSearchQuery: observable.ref,
// computed
currentWorkspace: computed,
workspacesCreatedByCurrentUser: computed,
@@ -52,6 +57,7 @@ export class WorkspaceRootStore implements IWorkspaceRootStore {
getWorkspaceBySlug: action,
getWorkspaceById: action,
// actions
setWorkspaceActiveCyclesSearchQuery: action,
fetchWorkspaces: action,
createWorkspace: action,
updateWorkspace: action,
@@ -102,6 +108,14 @@ export class WorkspaceRootStore implements IWorkspaceRootStore {
*/
getWorkspaceById = (workspaceId: string) => this.workspaces?.[workspaceId] || null; // TODO: use undefined instead of null
/**
* Sets search query
* @param query
*/
setWorkspaceActiveCyclesSearchQuery = (query: string) => {
this.workspaceActiveCyclesSearchQuery = query;
};
/**
* fetch user workspaces from API
*/