Skip to content

Implementation of the feature #6642 Calendar/Kanban View for Individual User across the Projects#8588

Open
anton-v-a wants to merge 21 commits intomakeplane:previewfrom
anton-v-a:feat-workspace-kanban-calendar-view
Open

Implementation of the feature #6642 Calendar/Kanban View for Individual User across the Projects#8588
anton-v-a wants to merge 21 commits intomakeplane:previewfrom
anton-v-a:feat-workspace-kanban-calendar-view

Conversation

@anton-v-a
Copy link

@anton-v-a anton-v-a commented Jan 27, 2026

Pull Request: Calendar and Kanban Layouts for Workspace Views

Summary

This PR adds Calendar and Kanban layout support to workspace-level views in Plane. Previously, workspace views (All Issues, Assigned, Created, Subscribed) only supported the Spreadsheet layout. Users can now visualize their work across all projects in time-based (Calendar) and workflow-based (Kanban) formats.

Key Features

  • Calendar Layout: View issues grouped by target date with month/week navigation
  • Kanban Layout: View issues grouped by state group, priority, project, or labels
  • "No Date" Section: Collapsible section in Calendar showing issues without a target date
  • Drag-and-Drop: Move issues between dates (Calendar) or columns (Kanban)
  • Quick Add: Create issues directly from Calendar/Kanban with project selection
  • Cross-Project State Mapping: Intelligent state resolution when moving issues between state groups

Changes Overview

Frontend (apps/web)

New Components

File Description
calendar/roots/workspace-root.tsx Workspace Calendar root component with "No Date" section support
kanban/roots/workspace-root.tsx Workspace Kanban root component with state group mapping
quick-add/workspace-root.tsx Quick add component with project dropdown for workspace views

Modified Components

File Changes
calendar/calendar.tsx Added "No Date" collapsible section, updated props for workspace support
roots/all-issue-layout-root.tsx Integrated Calendar and Kanban layout rendering
utils.tsx Added findStateByGroup() utility for cross-project state mapping, enhanced drag-drop handling for state_detail.group
issue-layout-HOC.tsx Updated layout type handling

Store Changes

File Changes
workspace/filter.store.ts Auto-set state_detail.group as default group_by for Kanban, layout switch handling
workspace/issue.store.ts Added grouped pagination support, undefined response handling for aborted requests
helpers/base-issues.store.ts Added addIssue() method exposure for issue map updates

Hooks & Services

File Changes
use-issues.ts Exposed addIssuesToMap function for adding fetched issues to the store
workspace.service.ts Updated return type to handle undefined (aborted requests)

Backend (apps/api)

API Changes

File Changes
views/view/base.py Added grouped pagination to WorkspaceViewIssuesViewSet, backward-compatible with existing clients
utils/filters/filterset.py Added target_date__isnull and start_date__isnull filters for "No Date" queries

Packages

Constants (packages/constants)

File Changes
issue/filter.ts Added WORKSPACE_KANBAN_GROUP_BY_OPTIONS, updated layout configs for my_issues filter type
issue/common.ts Added "calendar" to WORKSPACE_ACTIVE_LAYOUTS

Technical Implementation Details

1. State Group to State ID Mapping

Workspace views group issues by state_detail.group (e.g., "backlog", "started", "completed") instead of specific state_id because states are project-specific. When an issue is dragged to a new state group column:

  1. The findStateByGroup() utility finds a matching state in the issue's project
  2. Prefers the default state for that group, falls back to first match
  3. Updates the issue's state_id to the resolved state
export const findStateByGroup = (
  projectStates: IState[] | undefined,
  targetStateGroup: string
): IState | undefined => {
  if (!projectStates) return undefined;
  return (
    projectStates.find((s) => s.group === targetStateGroup && s.default) ||
    projectStates.find((s) => s.group === targetStateGroup)
  );
};

2. "No Date" Section Architecture

The Calendar view fetches issues without target_date separately from date-range issues:

  • Separate API call: Uses target_date__isnull=true filter
  • Independent state: Managed via noDateIssueIds and noDateTotalCount
  • Cached results: Only re-fetches when appliedFiltersKey changes
  • Collapsible UI: Users can show/hide the section

3. Grouped Pagination for Kanban

The backend WorkspaceViewIssuesViewSet now supports grouped pagination:

  • Without group_by: Returns flat list (backward compatible)
  • With group_by: Returns grouped structure with per-group pagination
  • Uses existing GroupedOffsetPaginator from project issue views

4. Quick Add with Project Selection

Workspace views require project selection before creating an issue:

  • Project dropdown appears in quick add form
  • State is auto-resolved based on the column's state group
  • Pre-populated data (target_date, priority, etc.) is applied

API Changes

WorkspaceViewIssuesViewSet

Endpoint: GET /api/v1/workspaces/{workspace_slug}/views/issues/

New Query Parameters:

Parameter Type Description
group_by string Group results by field (e.g., state_detail.group, priority)
sub_group_by string Sub-group results (optional)
target_date__isnull boolean Filter issues without target date

Backward Compatibility: ✅ Existing clients receive flat list when group_by is not provided.


Testing Checklist

Calendar Layout

  • Month view displays issues on correct dates
  • Week view navigation works correctly
  • "No Date" section shows only issues without target_date
  • "No Date" section collapses/expands
  • Drag-drop changes issue target_date
  • Filters apply correctly across all projects

Kanban Layout

  • Groups by state_detail.group correctly
  • Groups by priority correctly
  • Groups by project correctly
  • Groups by labels correctly
  • Drag-drop between state groups updates issue state
  • Empty groups show/hide based on display filter

Cross-cutting

  • Layout persistence works (switching between layouts remembers choice)
  • Loading states display correctly during layout switch
  • Aborted requests don't cause errors

Related Issues


Migration Notes

No database migrations required. The feature uses existing issue fields and adds optional query parameters to existing endpoints.


Rollback Plan

  1. Revert this PR
  2. No database changes to rollback
  3. Users will see only Spreadsheet layout in workspace views (previous behavior)

Summary by CodeRabbit

  • New Features

    • Workspace-level Calendar and Kanban roots, plus workspace quick-add for issues.
    • Grouped issue listing with paginated group/sub-group support and validation.
    • "No Date" collapsible section and null-date filters for target/start dates.
  • Enhancements

    • Improved drag-and-drop handling for state-group grouping.
    • Layout-aware filter defaults and safer handling of canceled workspace fetches.
    • Calendar/Kanban layout improvements and streamlined filter types.

@CLAassistant
Copy link

CLAassistant commented Jan 27, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds workspace-level calendar and kanban roots, grouped issue listing with validated grouping/pagination, workspace quick-add with state-group resolution, layout-aware filter/store adjustments, new helper utilities and store APIs, UI/type refinements across calendar/kanban/quick-add components, and backend grouping/filter enhancements.

Changes

Cohort / File(s) Summary
Backend grouping & filters
apps/api/plane/app/views/view/base.py, apps/api/plane/utils/filters/filterset.py
Add group_by/sub_group_by validation, grouping pipeline with grouped/sub-grouped paginators, preserve pre-annotation total counts; add target_date__isnull and start_date__isnull Boolean filters.
Workspace calendar UI
apps/web/ce/components/views/helper.tsx, apps/web/core/components/issues/issue-layouts/calendar/...
Add WorkspaceCalendarRoot, unify calendar components to IBaseIssueFilterStore, add No Date collapsible section and new calendar props for no-date/quick-add handling.
Workspace kanban UI
apps/web/core/components/issues/issue-layouts/kanban/roots/workspace-root.tsx
Add WorkspaceKanBanRoot with grouping, drag-and-drop, delete-drop area, permission checks, group collapse/expand and pagination helpers.
Workspace quick-add
apps/web/core/components/issues/issue-layouts/quick-add/*
Add WorkspaceQuickAddIssueRoot, export re-exports, workspace quick-add flow with state_group→state_id resolution and optimistic create.
Layout & utilities
apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx, .../issue-layout-HOC.tsx, .../utils.tsx
Layout-aware fetch logic (skip calendar, enable grouping for kanban), cached loader lookup in HOC, add findStateByGroup and map state_detail.group in drag/drop.
Stores & hooks (issue store)
apps/web/core/store/issue/helpers/base-issues.store.ts, apps/web/core/store/issue/workspace/issue.store.ts, apps/web/core/hooks/store/use-issues.ts
Add clearIssueIds() and setLoader() to base store; add addIssuesToMap() helper; implement quickAddIssue with optimistic update and abort handling.
Stores & hooks (actions, drag-drop)
apps/web/core/hooks/use-issues-actions.tsx, apps/web/core/hooks/use-group-dragndrop.ts
Expose archiveIssue, quickAddIssue; fetchNextIssues accepts optional viewId; include EIssuesStoreType.GLOBAL for DND; global fetches resolve effective viewId.
Workspace filter store
apps/web/core/store/issue/workspace/filter.store.ts
Make updateFilterExpression and updateFilters synchronous (void); apply layout-aware defaults (kanban/calendar), clear issue IDs before layout changes, use fire-and-forget fetches.
Service & API changes
apps/web/core/services/workspace.service.ts
getViewIssues may return undefined for aborted requests instead of throwing; otherwise unchanged.
Constants & config
packages/constants/src/issue/common.ts, packages/constants/src/issue/filter.ts, apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx
Add "state_detail.group" to drag-allowed groups; introduce WORKSPACE_KANBAN_GROUP_BY_OPTIONS and expand ISSUE_DISPLAY_FILTERS_BY_PAGE; remove workspaceSlug prop usage in a header child.
Type and small refactors
calendar dropdowns/day-tile/header/week-days, issue-layout HOC, others
Replace multiple union issue-filter-store types with IBaseIssueFilterStore, adjust some effect deps, cache loader lookup in HOC, minor type and import updates.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WorkspaceCalendarRoot
    participant FilterStore
    participant IssueStore
    participant WorkspaceService
    participant CalendarChart

    User->>WorkspaceCalendarRoot: Mount (globalViewId)
    WorkspaceCalendarRoot->>FilterStore: getAppliedFilters / layout & date range
    WorkspaceCalendarRoot->>IssueStore: fetchIssues(date range, viewId)
    IssueStore->>WorkspaceService: GET /view/issues (date range)
    WorkspaceService-->>IssueStore: TIssuesResponse
    IssueStore-->>WorkspaceCalendarRoot: Issues loaded
    WorkspaceCalendarRoot->>WorkspaceService: GET /view/issues (target_date__isnull)
    WorkspaceService-->>WorkspaceCalendarRoot: No-date issues
    WorkspaceCalendarRoot->>IssueStore: addIssuesToMap(no-date issues)
    WorkspaceCalendarRoot->>CalendarChart: render(issues, handlers)
    User->>CalendarChart: Drag / Toggle No-Date
    CalendarChart->>WorkspaceCalendarRoot: handleDragAndDrop / toggle
    WorkspaceCalendarRoot->>IssueStore: update issue / call actions
    WorkspaceCalendarRoot->>FilterStore: updateFilters (collapse/group)
Loading
sequenceDiagram
    participant User
    participant WorkspaceKanBanRoot
    participant FilterStore
    participant IssueStore
    participant WorkspaceService
    participant KanBan

    User->>WorkspaceKanBanRoot: Mount (globalViewId)
    WorkspaceKanBanRoot->>FilterStore: get display_filters (group_by)
    WorkspaceKanBanRoot->>IssueStore: fetchIssues/grouped (viewId)
    IssueStore->>WorkspaceService: GET /view/issues (grouping)
    WorkspaceService-->>IssueStore: TIssuesResponse (grouped)
    WorkspaceKanBanRoot->>KanBan: render(groups, permissions)
    User->>KanBan: Drag to delete
    KanBan->>WorkspaceKanBanRoot: handleDragAndDrop
    WorkspaceKanBanRoot->>IssueStore: archiveIssue(projectId, issueId)
    IssueStore-->>WorkspaceKanBanRoot: success
    KanBan->>FilterStore: updateFilters (collapse state)
Loading
sequenceDiagram
    participant User
    participant WorkspaceQuickAddRoot
    participant ProjectSelector
    participant StateResolver
    participant IssueStore
    participant WorkspaceService

    User->>WorkspaceQuickAddRoot: Open quick-add
    WorkspaceQuickAddRoot->>ProjectSelector: select project (auto if none)
    WorkspaceQuickAddRoot->>StateResolver: resolve state_detail.group -> state_id
    StateResolver->>WorkspaceService: getProjectStates(projectId)
    WorkspaceService-->>StateResolver: project states
    StateResolver-->>WorkspaceQuickAddRoot: resolved state_id
    User->>WorkspaceQuickAddRoot: Submit form
    WorkspaceQuickAddRoot->>IssueStore: quickAddIssue(projectId, payload)
    IssueStore->>IssueStore: add temp issue to map
    IssueStore->>WorkspaceService: POST /issues (create)
    WorkspaceService-->>IssueStore: created issue
    IssueStore->>WorkspaceQuickAddRoot: replace temp with real issue
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • prateekshourya29

Poem

🐰 Hopping through workspace lanes and dates,

Kanban stacks and calendar gates.
I found lost issues with a twitch of my nose,
Quick-add a carrot where the state-group grows.
A joyful hop — grouped views bloom, onward we go!

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being implemented: Calendar/Kanban layouts for workspace-level views across projects.
Description check ✅ Passed The PR description is comprehensive with detailed sections on key features, changes overview, technical implementation, API changes, testing checklist, and related issues.
Linked Issues check ✅ Passed The PR successfully implements all core objectives from issue #6642: Calendar and Kanban views for workspace-level issues, visibility into user workload/occupancy via calendar, and cross-project task timelines.
Out of Scope Changes check ✅ Passed All changes are within scope: frontend components/stores for calendar/kanban layouts, backend pagination support, filter additions, constants for layout options, and quick-add functionality—all directly supporting the workspace view feature.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into preview

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/web/core/store/issue/workspace/filter.store.ts (1)

270-295: Consider consolidating the duplicate sub_group_by nullification.

The group_by === sub_group_by check for kanban layout appears twice: once before applyLayoutDefaults (lines 270-276) and once after (lines 289-295). The first handles user-explicit values, the second handles values that applyLayoutDefaults may have changed. While functionally correct, the first check is redundant — applyLayoutDefaults doesn't change sub_group_by, so if they matched before, they'll still match after, and the second block covers it. You could remove lines 270-276 without behavior change.

♻️ Suggested simplification
          // set sub_group_by to null if group_by is set to null
          if (_filters.displayFilters.group_by === null) {
            _filters.displayFilters.sub_group_by = null;
            updatedDisplayFilters.sub_group_by = null;
          }
-          // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same
-          if (
-            _filters.displayFilters.layout === "kanban" &&
-            _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by
-          ) {
-            _filters.displayFilters.sub_group_by = null;
-            updatedDisplayFilters.sub_group_by = null;
-          }
           // Apply layout-specific defaults (kanban group_by, calendar config)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/web/core/hooks/use-issues-actions.tsx (1)

687-699: Keep pagination viewId consistent with fetchIssues override.

fetchIssues now accepts an explicit viewId, but fetchNextIssues still uses the router globalViewId. If callers pass a viewId (workspace-level views), pagination can no-op or page the wrong list. Consider mirroring the same effectiveViewId logic in fetchNextIssues.

💡 Suggested fix
-  const fetchNextIssues = useCallback(
-    async (groupId?: string, subGroupId?: string) => {
-      if (!workspaceSlug || !globalViewId) return;
-      return issues.fetchNextIssues(workspaceSlug.toString(), globalViewId.toString(), groupId, subGroupId);
-    },
-    [issues.fetchIssues, workspaceSlug, globalViewId]
-  );
+  const fetchNextIssues = useCallback(
+    async (groupId?: string, subGroupId?: string, viewId?: string) => {
+      const effectiveViewId = viewId ?? globalViewId;
+      if (!workspaceSlug || !effectiveViewId) return;
+      return issues.fetchNextIssues(workspaceSlug.toString(), effectiveViewId.toString(), groupId, subGroupId);
+    },
+    [issues.fetchIssues, workspaceSlug, globalViewId]
+  );
-  fetchNextIssues: (groupId?: string, subGroupId?: string) => Promise<TIssuesResponse | undefined>;
+  fetchNextIssues: (groupId?: string, subGroupId?: string, viewId?: string) => Promise<TIssuesResponse | undefined>;
apps/web/core/services/workspace.service.ts (1)

266-283: Fix type annotation in workspace-root.tsx for getViewIssues response.

The getViewIssues method now returns Promise<TIssuesResponse | undefined> for canceled requests, but workspace-root.tsx line 133 declares the response as const response: TIssuesResponse (missing | undefined). This will cause type errors in strict mode. Update the type to TIssuesResponse | undefined or remove the explicit type annotation to infer it correctly. The runtime guard if (response && response.results) is present but the type declaration is incorrect.

apps/web/core/store/issue/workspace/filter.store.ts (1)

248-265: Re-check sub_group_by after normalizing group_by.
If group_by is forced to "state_detail.group", sub_group_by can still equal it, bypassing the earlier guard. Consider validating again after the normalization.

🔧 Suggested fix
          if (_filters.displayFilters.layout === "kanban") {
            if (
              !_filters.displayFilters.group_by ||
              !WORKSPACE_KANBAN_GROUP_BY_OPTIONS.includes(
                _filters.displayFilters.group_by as typeof WORKSPACE_KANBAN_GROUP_BY_OPTIONS[number]
              )
            ) {
              _filters.displayFilters.group_by = "state_detail.group";
              updatedDisplayFilters.group_by = "state_detail.group";
            }
+           if (_filters.displayFilters.group_by === _filters.displayFilters.sub_group_by) {
+             _filters.displayFilters.sub_group_by = null;
+             updatedDisplayFilters.sub_group_by = null;
+           }
          }
apps/web/core/components/issues/issue-layouts/calendar/day-tile.tsx (1)

77-123: Avoid using ref .current in useEffect dependency array.

dayTileRef?.current in the dependency array won't trigger re-renders when the ref changes since refs are mutable and don't cause component updates. The ref object itself (dayTileRef) is stable across renders.

Proposed fix
-  }, [dayTileRef?.current, formattedDatePayload]);
+  }, [formattedDatePayload, handleDragAndDrop, issues]);

Note: You may also want to include handleDragAndDrop and issues in the dependency array since they're used inside the effect, or wrap them in useCallback/memoize appropriately to prevent stale closures.

🤖 Fix all issues with AI agents
In `@apps/api/plane/app/views/view/base.py`:
- Around line 246-247: Replace the unsafe deepcopy of the Django QuerySet:
instead of using copy.deepcopy(issue_queryset) to create
filtered_issue_queryset, call issue_queryset.all() to produce a new, independent
QuerySet (and remove the now-unused import copy from the top of the file if it's
no longer referenced). Ensure this change targets the filtered_issue_queryset
assignment where issue_queryset is referenced.

In
`@apps/web/core/components/issues/issue-layouts/calendar/roots/workspace-root.tsx`:
- Around line 103-165: The no-date fetch (fetchNoDateIssues) currently uses
perPageCount: 50 and sets setNoDateTotalCount(issueIds.length), which caps and
misreports totals; change it to read TIssuesResponse.total_count for total count
and implement pagination/load-more using the same pattern as the main calendar
(use workspaceService.getViewIssues response.next_page_results / cursors and a
loadMoreNoDateIssues handler or reuse loadMoreIssues) to request additional
pages instead of relying on a single 50-item request; accumulate results by
appending new issues to the existing no-date IDs (setNoDateIssueIds) and calling
addIssuesToMap for each page, and only fall back to client-side filtering of
issue.target_date while preserving the API pagination cursors rather than
truncating by array.length.

In
`@apps/web/core/components/issues/issue-layouts/kanban/roots/workspace-root.tsx`:
- Around line 205-218: handleCollapsedGroups currently mutates the store-backed
collapsedGroups array with push(), which can cause non-atomic updates; instead
create a new array before calling updateFilters: read the existing array from
issuesFilter?.issueFilters?.kanbanFilters?.[toggle] into collapsedGroups, then
if value is included produce a new array using filter to remove it, otherwise
produce a new array by concatenating the value (e.g., [...collapsedGroups,
value]); pass that new array to updateFilters (function updateFilters) so the
original store array is never mutated in place.
- Around line 190-203: The current handleDeleteIssue swallows errors by catching
all exceptions and always resolving, preventing DeleteIssueModal from receiving
rejections and showing error toasts; update handleDeleteIssue so that you await
removeIssue(draggedIssue.project_id, draggedIssueId) and only call
setDeleteIssueModal(false) and setDraggedIssueId(undefined) on success, but do
not swallow failures—either remove the try/catch entirely or rethrow the caught
error in the catch block (keep references to handleDeleteIssue, removeIssue,
setDeleteIssueModal, setDraggedIssueId, and DeleteIssueModal to locate the
change).

In `@apps/web/core/components/issues/issue-layouts/quick-add/workspace-root.tsx`:
- Around line 69-87: The logic in resolvedPrePopulatedData (useMemo) currently
retains "state_detail.group" when no matching state is found; update it so you
always remove the "state_detail.group" key from prePopulatedData and only add
state_id when findStateByGroup(projectStates, stateGroup) returns a targetState;
locate the resolution in resolvedPrePopulatedData (references:
selectedProjectId, prePopulatedData, getProjectStates, findStateByGroup, TIssue)
and return the spread rest without the "state_detail.group" field in both
branches, conditionally merging state_id = targetState.id only when targetState
exists.

In `@apps/web/core/components/issues/issue-layouts/utils.tsx`:
- Around line 563-597: When handling groupBy === "state_detail.group", guard
against a missing project state list by checking the result of
getProjectStates(sourceIssue.project_id) and if projectStates is undefined (i.e.
sourceIssue.project_id is falsy or no states returned) throw an explicit Error
(or otherwise block the drop) before calling findStateByGroup; update the logic
around getProjectStates, findStateByGroup, updatedIssue and issueUpdates to
ensure you only set state_id and issueUpdates when a valid targetState is found
and otherwise reject the operation with a clear error message.
🧹 Nitpick comments (5)
apps/web/ce/components/views/helper.tsx (1)

8-12: Unused workspaceSlug prop in GlobalViewLayoutSelection.

The workspaceSlug property is defined in TLayoutSelectionProps but is not destructured or used in the component implementation. If this is intentional for API consistency, consider adding a comment. Otherwise, remove it from the type definition to keep the interface clean.

♻️ Suggested fix if the prop is not needed
 export type TLayoutSelectionProps = {
   onChange: (layout: EIssueLayoutTypes) => void;
   selectedLayout: EIssueLayoutTypes;
-  workspaceSlug: string;
 };

Also applies to: 21-22

apps/api/plane/app/views/view/base.py (1)

266-339: Add validation for allowed group_by and sub_group_by field values.

The code correctly prevents group_by and sub_group_by from being equal, but does not validate that these values are from the set of supported grouping fields (state_id, priority, state__group, cycle_id, project_id, labels__id, assignees__id, issue_module__module_id, target_date, start_date, created_by). Invalid field names are silently ignored—issue_group_values returns an empty list and results fail to group properly—leaving users without feedback that their grouping parameter was unsupported. Adding validation to reject invalid field names with a 400 error would improve clarity and user experience.

apps/web/core/components/issues/issue-layouts/calendar/calendar.tsx (1)

241-276: Add ARIA state for the “No Date” toggle.

This makes the collapsible section discoverable to screen readers.

♿ Proposed accessibility tweak
-                <button
+                <button
                   type="button"
+                  aria-expanded={!isNoDateCollapsed}
+                  aria-controls="no-date-section"
                   className="flex w-full items-center gap-2 px-4 py-2 bg-layer-1 cursor-pointer hover:bg-layer-2 text-left"
                   onClick={() => setIsNoDateCollapsed(!isNoDateCollapsed)}
                 >
                   <ChevronRight
                     className={cn("size-4 text-tertiary transition-transform", {
                       "rotate-90": !isNoDateCollapsed,
                     })}
                   />
                   <span className="text-13 font-medium text-secondary">No Date</span>
                   <span className="text-11 text-tertiary">({noDateIssueCount ?? noDateIssueIds.length})</span>
                 </button>
                 {!isNoDateCollapsed && (
-                  <div className="px-4 py-2 bg-surface-1">
+                  <div id="no-date-section" className="px-4 py-2 bg-surface-1">
                     <CalendarIssueBlocks
                       date={new Date()}
                       issueIdList={noDateIssueIds}
                       loadMoreIssues={() => {}}
apps/web/core/components/issues/issue-layouts/kanban/roots/workspace-root.tsx (1)

81-92: Avoid duplicate permission scans per render.

Compute once and reuse for the two prop expressions.

♻️ Proposed small refactor
   const canCreateIssues = useCallback(() => {
     if (!joinedProjectIds || joinedProjectIds.length === 0) return false;
     return joinedProjectIds.some((projectId) =>
       allowPermissions(
         [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
         EUserPermissionsLevel.PROJECT,
         workspaceSlug?.toString(),
         projectId
       )
     );
   }, [joinedProjectIds, allowPermissions, workspaceSlug]);

+  const canCreateIssuesValue = canCreateIssues();
...
-                enableQuickIssueCreate={enableQuickAdd && canCreateIssues()}
+                enableQuickIssueCreate={enableQuickAdd && canCreateIssuesValue}
...
-                disableIssueCreation={!enableIssueCreation || !canCreateIssues()}
+                disableIssueCreation={!enableIssueCreation || !canCreateIssuesValue}

Also applies to: 223-223, 270-273

apps/web/core/components/issues/issue-layouts/calendar/roots/workspace-root.tsx (1)

51-62: Compute canCreateIssues once per render.

Avoid repeating the permission scan for both creation props.

♻️ Small reuse improvement
   const canCreateIssues = useCallback(() => {
     if (!joinedProjectIds || joinedProjectIds.length === 0) return false;
     return joinedProjectIds.some((projectId) =>
       allowPermissions(
         [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
         EUserPermissionsLevel.PROJECT,
         workspaceSlug?.toString(),
         projectId
       )
     );
   }, [joinedProjectIds, allowPermissions, workspaceSlug]);

+  const canCreateIssuesValue = canCreateIssues();
...
-        enableQuickIssueCreate={enableQuickAdd && canCreateIssues()}
-        disableIssueCreation={!enableIssueCreation || !canCreateIssues()}
+        enableQuickIssueCreate={enableQuickAdd && canCreateIssuesValue}
+        disableIssueCreation={!enableIssueCreation || !canCreateIssuesValue}

Also applies to: 274-275

…ent pagination

Mirror the effectiveViewId logic from fetchIssues in fetchNextIssues so
that workspace-level views using an explicit viewId paginate correctly.
Also fix dependency array referencing fetchIssues instead of fetchNextIssues.
…Issues response

Let TypeScript infer the return type (TIssuesResponse | undefined) instead
of explicitly annotating as TIssuesResponse, which is incorrect since
getViewIssues can return undefined for canceled requests.
…nban

When group_by is forced to state_detail.group for workspace kanban views,
sub_group_by could still equal it, bypassing the earlier duplicate guard.
Added a post-normalization check to nullify sub_group_by in that case.
Remove dayTileRef.current from dependency array (ref mutations don't
trigger re-renders) and add handleDragAndDrop and issues to prevent
stale closures in the drop handler.
The prop was defined in the type and passed by the caller but never
used in GlobalViewLayoutSelection. Removed from both the type and
the call site.
Reject invalid field names with a 400 error instead of silently
returning empty groups. Validates against the set of fields supported
by issue_group_values.
Replace useCallback returning a function with useMemo returning a
boolean to avoid duplicate permission scans across joinedProjectIds
on every render. Applied to both kanban and calendar workspace roots.
Use response.total_count instead of issueIds.length for accurate count
when there are more no-date issues than the page size. Increased
perPageCount from 50 to 500 to cover most real-world cases without
needing full cursor-based pagination.
Use .finally() for cleanup instead of try/catch that swallowed errors.
This allows DeleteIssueModal to surface error toasts on failed deletes.
Use spread to create a new array instead of push() which mutates
the MobX store array before updateFilters runs.
Remove the synthetic state_detail.group key regardless of whether a
matching state is found, preventing the API from receiving an
unrecognized field.
Throw early when sourceIssue.project_id is falsy instead of silently
skipping the state_id update. Also simplify the else branch to always
throw when no matching state is found, since projectStates is now
guaranteed to be defined.
@anton-v-a anton-v-a changed the title Implementation of the feature #6642 Calendar/Kanban View for Individu… Implementation of the feature #6642 Calendar/Kanban View for Individual User across the Projects Jan 31, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/api/plane/app/views/view/base.py`:
- Around line 278-281: The code always calls issue_queryset_grouper even when
grouping isn't requested, causing expensive subquery annotations (e.g.,
assignee_ids, label_ids, module_ids) to be added unnecessarily; modify
issue_queryset_grouper to return the original queryset immediately when both
group_by and sub_group_by are falsy (add an early return at the top of
issue_queryset_grouper) so no annotations are applied when grouping is not used,
and keep the existing behavior for the rest of the function.
🧹 Nitpick comments (1)
apps/web/core/store/issue/workspace/filter.store.ts (1)

210-220: Consider logging typed errors with console.error.
Normalizing unknown error values improves log quality and consistency.

♻️ Suggested change
   } catch (error) {
-    console.log("error while updating rich filters", error);
-    throw error;
+    const err = error instanceof Error ? error : new Error(String(error));
+    console.error("error while updating rich filters", err);
+    throw err;
   }

As per coding guidelines: Use try-catch with proper error types and log errors appropriately.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/core/store/issue/workspace/filter.store.ts`:
- Around line 216-217: The fire-and-forget calls using void
this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug,
viewId, "mutation") (and the same pattern elsewhere) can reject and cause
unhandled promise rejections; update each such call (including the occurrences
around lines with workspaceSlug/viewId and the other calls at the noted
locations) to append a .catch(...) handler that logs or swallows the error via
your logger (e.g., processLogger or root logger) so rejections are handled;
ensure you reference the same function name fetchIssuesWithExistingPagination
and owner rootIssueStore.workspaceIssues when adding the .catch for all
fire-and-forget invocations.
🧹 Nitpick comments (2)
apps/web/core/store/issue/workspace/filter.store.ts (2)

186-195: Duplicated kanban/calendar default logic between fetchFilters and updateFilters.

The kanban group_by defaulting (lines 186-190 ≈ 257-264) and calendar defaults (lines 193-195 ≈ 272-275) are repeated verbatim. Consider extracting a shared helper (e.g., applyLayoutDefaults(displayFilters)) to keep the two paths in sync and reduce the chance of future divergence.

Also applies to: 255-275


296-310: Empty if branch is a code smell — prefer inverting the condition.

The calendar branch (lines 296-298) is intentionally empty. Inverting the conditions or using an early break/guard avoids the empty block and makes intent clearer.

Suggested refactor
-          if (updatedDisplayFilters.layout === "calendar") {
-            // Calendar layout needs date-range parameters that only the component can provide
-            // Don't fetch here - let the calendar component handle it
-          } else if (updatedDisplayFilters.layout) {
+          if (updatedDisplayFilters.layout && updatedDisplayFilters.layout !== "calendar") {
             // Layout is changing to kanban or spreadsheet - fetch with correct canGroup
             const needsGrouping = _filters.displayFilters.layout === "kanban";
-            void this.rootIssueStore.workspaceIssues.fetchIssues(
+            this.rootIssueStore.workspaceIssues.fetchIssues(
               workspaceSlug,
               viewId,
               "init-loader",
               { canGroup: needsGrouping, perPageCount: needsGrouping ? 30 : 100 }
-            );
-          } else {
-            void this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug, viewId, "mutation");
+            ).catch((err) => console.error("Failed to fetch issues after layout change", err));
+          } else if (!updatedDisplayFilters.layout) {
+            this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug, viewId, "mutation")
+              .catch((err) => console.error("Failed to fetch issues after filter change", err));
           }

anton-v-a and others added 4 commits February 14, 2026 19:43
…re-and-forget calls

Replace void-prefixed fire-and-forget calls with .catch() handlers to
prevent unhandled promise rejections from background fetches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…youtDefaults helper

Deduplicate the kanban group_by defaulting and calendar config defaulting
logic that was repeated in both fetchFilters and updateFilters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…endar root

Change import from non-existent @/plane-web/services to
@/services/workspace.service which is where WorkspaceService is defined.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/core/store/issue/workspace/filter.store.ts (1)

336-339: 🛠️ Refactor suggestion | 🟠 Major

Use the STATIC_VIEW_TYPES constant instead of the hardcoded array.

STATIC_VIEW_TYPES is imported at line 24 and already used at lines 164 and 198 in this file. The hardcoded ["all-issues", "assigned", "created", "subscribed"] at line 336 duplicates the constant definition and risks drifting out of sync if it changes.

Suggested fix
-          if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
+          if (STATIC_VIEW_TYPES.includes(viewId))
🤖 Fix all issues with AI agents
In `@apps/web/core/store/issue/workspace/filter.store.ts`:
- Around line 297-302: Summary: avoid clearing issues when the incoming layout
is identical to the current one to prevent unnecessary loader flashes. Fix: in
the branch that currently checks updatedDisplayFilters.layout, compare the new
layout value against the existing layout (e.g., this.displayFilters.layout) and
only call this.rootIssueStore.workspaceIssues.clearIssueIds() when they differ;
keep the existing behavior of clearing BEFORE applying the layout update (so
IssueLayoutHOC sees undefined issueCount) but skip the clear if no actual
change.
🧹 Nitpick comments (4)
apps/web/core/components/issues/issue-layouts/calendar/roots/workspace-root.tsx (2)

25-31: Unused isDefaultView prop.

isDefaultView is declared in Props but never referenced in the component body (only globalViewId is destructured on line 31). Either remove it from the type or use it.

Proposed fix
 type Props = {
-  isDefaultView: boolean;
   globalViewId: string;
 };

192-224: handleDragAndDrop is not memoized — new reference every render.

This async handler is passed as a prop to CalendarChart. Without useCallback, a new closure is created on every render, which can trigger unnecessary re-renders of the chart (and its children). The other callbacks (loadMoreIssues, getPaginationData, getGroupIssueCount, canEditProperties) are already wrapped in useCallback.

Wrap in useCallback
-  const handleDragAndDrop = async (
+  const handleDragAndDrop = useCallback(async (
     issueId: string | undefined,
     issueProjectId: string | undefined,
     sourceDate: string | undefined,
     destinationDate: string | undefined
   ) => {
     if (!issueId || !destinationDate || !sourceDate || !issueProjectId) return;
 
     if (!canEditPropertiesBasedOnProject(issueProjectId)) {
       setToast({
         title: "Permission denied",
         type: TOAST_TYPE.ERROR,
         message: "You don't have permission to edit this issue",
       });
       return;
     }
 
     await handleDragDrop(
       issueId,
       sourceDate,
       destinationDate,
       workspaceSlug?.toString(),
       issueProjectId,
       updateIssue
     ).catch((err: { detail?: string }) => {
       setToast({
         title: "Error!",
         type: TOAST_TYPE.ERROR,
         message: err?.detail ?? "Failed to perform this action",
       });
     });
-  };
+  }, [canEditPropertiesBasedOnProject, workspaceSlug, updateIssue]);
apps/web/core/store/issue/workspace/filter.store.ts (2)

86-106: Consider using EIssueLayoutTypes enum values instead of string literals.

Lines 92 and 103 use "kanban" and "calendar" string literals, while line 127 uses EIssueLayoutTypes.SPREADSHEET. Using the enum consistently (e.g., EIssueLayoutTypes.KANBAN) prevents silent breakage if the enum values ever change, and keeps the file self-consistent.

The same applies to other string literal layout comparisons on lines 271, 290, 317, and 319.


319-324: Extract magic numbers into named constants.

30 and 100 for perPageCount are used without explanation. Named constants clarify intent and prevent inconsistencies if the same values appear elsewhere.

Example
+const KANBAN_PER_PAGE_COUNT = 30;
+const LIST_PER_PAGE_COUNT = 100;
 // ...
-            { canGroup: needsGrouping, perPageCount: needsGrouping ? 30 : 100 }
+            { canGroup: needsGrouping, perPageCount: needsGrouping ? KANBAN_PER_PAGE_COUNT : LIST_PER_PAGE_COUNT }

… flash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature]: Calendar/Kabana View for Individual User across the Projects

2 participants