Compare commits

...
Author SHA1 Message Date
sriram veeraghantaandGitHub c1b1ba35c1 Merge pull request #3878 from makeplane/preview
release: v0.16-dev
2024-03-05 20:04:08 +05:30
M. PalanikannanandGitHub d99529b109 fix: crash while updating link text on the last node (#3871) 2024-03-04 20:33:16 +05:30
Vihar KuramaandGitHub 6eb7014ea4 chore: update github readme - content edits to feature section, new screenshots and added repo activity, contributors. (#3870)
* update github readme, add new features and deployment options

* add new product screenshots

* add contributors section to readme

* chore: update formatting
2024-03-04 20:06:53 +05:30
Anmol Singh BhatiaandGitHub 894de80f41 [WEB-616] fix: inbox issue navigation issue title and description event propagation (#3851)
* fix: inbox issue navigation issue title and description event propagation

* fix: inbox issue navigation issue title and description event propagation
2024-03-01 17:29:30 +05:30
Anmol Singh BhatiaandGitHub 4b706437d7 [WEB-619] fix: workspace all issue quick action (#3853)
* chore: custom menu dropdown menu items classname prop added

* fix: issue layout quick action z index fix
2024-03-01 17:23:26 +05:30
Anmol Singh BhatiaandGitHub e4bccea824 fix: multiple issue comment (#3854) 2024-03-01 17:20:36 +05:30
sriram veeraghanta d39f2526a2 chore: removing unnecessary lines 2024-03-01 13:49:50 +05:30
Bavisetti NarayanandGitHub bc6e48fcd6 chore: issue comment PATCH changes (#3850) 2024-03-01 13:33:23 +05:30
guru_sainathandGitHub e6f33eb262 [WEB-609] fix: header text overflow issue in kanban layout (#3848)
* fix: kanban header text overflow

* chore: updated condition
2024-02-29 20:29:02 +05:30
guru_sainathandGitHub 5cfebb8dae fix: issue description on last draft issue (#3846) 2024-02-29 19:41:30 +05:30
Anmol Singh BhatiaandGitHub e4988ee940 fix: issue sidebar and peek overview cycle select improvement (#3845) 2024-02-29 19:40:46 +05:30
sriram veeraghanta 850bf01d65 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-02-29 17:20:44 +05:30
Anmol Singh BhatiaandGitHub f183e389ea fix: module sidebar description duplicacy (#3844) 2024-02-29 17:20:17 +05:30
guru_sainathandGitHub 5d7c0a2a64 [WEB-600] fix: fixed sub-group-by issue count display in kanban layout header (#3843)
* fix: fixed subgroupby issue count at the header in kanban layout

* chore: code beautification
2024-02-29 17:19:51 +05:30
Anmol Singh BhatiaandGitHub 92e994990c fix: project sidebar favorite list logic updated (#3842) 2024-02-29 17:19:13 +05:30
guru_sainathandGitHub d1087820f6 [web-599] ui: kanban layout UI consistency enhancement for grouped display filters (#3841)
* ui: UI inconsistancy in kanban layout when we grouped and sub grouped display filters.

* ui: width update in the kanban block
2024-02-29 17:18:03 +05:30
Anmol Singh BhatiaandGitHub 65024fe5ec chore: priority icon none state hover state added (#3840) 2024-02-29 16:12:34 +05:30
Anmol Singh BhatiaandGitHub 62693abb09 chore: project emoji icon improvement (#3837) 2024-02-29 15:31:44 +05:30
guru_sainathandGitHub 9326fb0762 [WEB-601] feat: enhanced display filters grouping by cycles and modules in project issues (#3834)
* feat: implemented cycle and module for display filters groupBy and sunGroupBy in  project issues list and kanban layouts

* chore: Enabled drag ability for cycle and handled prepopulated data for quick add

* chore: disbaled drag ability for cycle

* chore: updated preloaded data

* chore: updated module and cycle store router dependancy to prop dependancy
2024-02-29 15:31:03 +05:30
56805203f1 [WEB-597] chore: dashboard overdue issues (#3832)
* fix bug of dashboard report overdue of all tasks #3815 (#3823)

* chore: pending issues filter

* chore: removed the Q parameter

* chore: dashboard widget overdue stats card redirection params updated

---------

Co-authored-by: AbId KhAn <abidkhan484@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2024-02-29 13:32:16 +05:30
Anmol Singh BhatiaandGitHub 39136d3a9f chore: spreadsheet layout dropdowns focus on toggle functionality added (#3833) 2024-02-29 13:23:37 +05:30
Anmol Singh BhatiaandGitHub 34301e4399 fix: app sidebar toggle (#3829) 2024-02-28 19:55:22 +05:30
guru_sainathandGitHub 51f795fbd7 [WEB-477] feat: enhanced project issue filtering by cycles and modules (#3830)
* feat: implemented cycle and module filter in project issues

* feat: implemented cycle and module filter in draft and archived issues
2024-02-28 19:34:29 +05:30
Anmol Singh BhatiaandGitHub 7abfbac479 chore: spreadsheet layout dropdown z index improvement (#3825) 2024-02-28 19:15:03 +05:30
Aaryan KhandelwalandGitHub c4028efd71 fix: usePage hook throws an error without projectId (#3827) 2024-02-28 19:14:15 +05:30
Aaryan KhandelwalandGitHub 0215b697a5 chore: update issue date icons (#3826) 2024-02-28 19:13:17 +05:30
sriram veeraghantaandGitHub 4566d6e80c Merge pull request #3697 from makeplane/preview
release: 0.15.4-dev
2024-02-19 19:30:06 +05:30
sriram veeraghantaandGitHub e8d359e625 Merge pull request #3674 from makeplane/preview
fix: build branch docker images push on release
2024-02-15 14:35:32 +05:30
sriram veeraghantaandGitHub 351eba8d61 Merge pull request #3671 from makeplane/preview
release: peek overview issue description initial load bug (#3670)
2024-02-15 03:25:30 +05:30
sriram veeraghantaandGitHub 1e27e37b51 Merge pull request #3666 from makeplane/preview
release: v0.15.2-dev
2024-02-14 19:41:55 +05:30
sriram veeraghantaandGitHub 7df2e9cf11 Merge pull request #3632 from makeplane/preview
release: v0.15.1-dev
2024-02-12 20:59:56 +05:30
sriram veeraghantaandGitHub c6e3f1b932 Merge pull request #3535 from makeplane/preview
release: 0.15-dev
2024-02-01 15:01:49 +05:30
74 changed files with 1173 additions and 641 deletions
+98 -53
View File
@@ -7,7 +7,7 @@
</p>
<h3 align="center"><b>Plane</b></h3>
<p align="center"><b>Flexible, extensible open-source project management</b></p>
<p align="center"><b>Open-source project management that unlocks customer value.</b></p>
<p align="center">
<a href="https://discord.com/invite/A92xrEGCge">
@@ -16,6 +16,13 @@
<img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
</p>
<p align="center">
<a href="http://www.plane.so"><b>Website</b></a> •
<a href="https://github.com/makeplane/plane/releases"><b>Releases</b></a> •
<a href="https://twitter.com/planepowers"><b>Twitter</b></a> •
<a href="https://docs.plane.so/"><b>Documentation</b></a>
</p>
<p>
<a href="https://app.plane.so/#gh-light-mode-only" target="_blank">
<img
@@ -33,56 +40,93 @@
</a>
</p>
Meet [Plane](https://plane.so). An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️.
Meet [Plane](https://plane.so). An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind. 🧘‍♀️
> Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
## ⚡️ Contributors Quick Start
### Prerequisite
## ⚡ Installation
Development system must have docker engine installed and running.
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account where we offer a hosted solution for users.
### Steps
If you want more control over your data prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
Setting up local environment is extremely easy and straight forward. Follow the below step and you will be ready to contribute
| Installation Methods | Documentation Link |
|-----------------|----------------------------------------------------------------------------------------------------------|
| Docker | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://docs.plane.so/docker-compose) |
| Kubernetes | [![Kubernetes](https://img.shields.io/badge/kubernetes-%23326ce5.svg?style=for-the-badge&logo=kubernetes&logoColor=white)](https://docs.plane.so/kubernetes) |
1. Clone the code locally using `git clone https://github.com/makeplane/plane.git`
1. Switch to the code folder `cd plane`
1. Create your feature or fix branch you plan to work on using `git checkout -b <feature-branch-name>`
1. Open terminal and run `./setup.sh`
1. Open the code on VSCode or similar equivalent IDE
1. Review the `.env` files available in various folders. Visit [Environment Setup](./ENV_SETUP.md) to know about various environment variables used in system
1. Run the docker command to initiate various services `docker compose -f docker-compose-local.yml up -d`
You are ready to make changes to the code. Do not forget to refresh the browser (in case id does not auto-reload)
Thats it!
## 🍙 Self Hosting
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/docker-compose) documentation page
`Instance admin` can configure instance settings using our [God-mode](https://docs.plane.so/instance-admin) feature.
## 🚀 Features
- **Issue Planning and Tracking**: Quickly create issues and add details using a powerful rich text editor that supports file uploads. Add sub-properties and references to issues for better organization and tracking.
- **Issue Attachments**: Collaborate effectively by attaching files to issues, making it easy for your team to find and share important project-related documents.
- **Layouts**: Customize your project view with your preferred layout - choose from List, Kanban, or Calendar to visualize your project in a way that makes sense to you.
- **Cycles**: Plan sprints with Cycles to keep your team on track and productive. Gain insights into your project's progress with burn-down charts and other useful features.
- **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to easily track and plan your project's progress.
- **Issues**: Quickly create issues and add details using a powerful, rich text editor that supports file uploads. Add sub-properties and references to problems for better organization and tracking.
- **Cycles**
Keep up your team's momentum with Cycles. Gain insights into your project's progress with burn-down charts and other valuable features.
- **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to track and plan your project's progress easily.
- **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks.
- **Pages**: Plane pages function as an AI-powered notepad, allowing you to easily document issues, cycle plans, and module details, and then synchronize them with your issues.
- **Command K**: Enjoy a better user experience with the new Command + K menu. Easily manage and navigate through your projects from one convenient location.
- **GitHub Sync**: Streamline your planning process by syncing your GitHub issues with Plane. Keep all your issues in one place for better tracking and collaboration.
- **Pages**: Plane pages, equipped with AI and a rich text editor, let you jot down your thoughts on the fly. Format your text, upload images, hyperlink, or sync your existing ideas into an actionable item or issue.
- **Analytics**: Get insights into all your Plane data in real-time. Visualize issue data to spot trends, remove blockers, and progress your work.
- **Drive** (*coming soon*): The drive helps you share documents, images, videos, or any other files that make sense to you or your team and align on the problem/solution.
## 🛠️ Contributors Quick Start
> Development system must have docker engine installed and running.
Setting up local environment is extremely easy and straight forward. Follow the below step and you will be ready to contribute
1. Clone the code locally using:
```
git clone https://github.com/makeplane/plane.git
```
2. Switch to the code folder:
```
cd plane
```
3. Create your feature or fix branch you plan to work on using:
```
git checkout -b <feature-branch-name>
```
4. Open terminal and run:
```
./setup.sh
```
5. Open the code on VSCode or similar equivalent IDE.
6. Review the `.env` files available in various folders.
Visit [Environment Setup](./ENV_SETUP.md) to know about various environment variables used in system.
7. Run the docker command to initiate services:
```
docker compose -f docker-compose-local.yml up -d
```
You are ready to make changes to the code. Do not forget to refresh the browser (in case it does not auto-reload).
Thats it!
## ❤️ Community
The Plane community can be found on [GitHub Discussions](https://github.com/orgs/makeplane/discussions), and our [Discord server](https://discord.com/invite/A92xrEGCge). Our [Code of conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community chanels.
Ask questions, report bugs, join discussions, voice ideas, make feature requests, or share your projects.
### Repo Activity
![Plane Repo Activity](https://repobeats.axiom.co/api/embed/2523c6ed2f77c082b7908c33e2ab208981d76c39.svg "Repobeats analytics image")
## 📸 Screenshots
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_views_dark_mode.webp"
src="https://ik.imagekit.io/w2okwbtu2/Issues_rNZjrGgFl.png?updatedAt=1709298765880"
alt="Plane Views"
width="100%"
/>
@@ -91,8 +135,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_issue_detail_dark_mode.webp"
alt="Plane Issue Details"
src="https://ik.imagekit.io/w2okwbtu2/Cycles_jCDhqmTl9.png?updatedAt=1709298780697"
width="100%"
/>
</a>
@@ -100,7 +143,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_cycles_modules_dark_mode.webp"
src="https://ik.imagekit.io/w2okwbtu2/Modules_PSCVsbSfI.png?updatedAt=1709298796783"
alt="Plane Cycles and Modules"
width="100%"
/>
@@ -109,7 +152,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_analytics_dark_mode.webp"
src="https://ik.imagekit.io/w2okwbtu2/Views_uxXsRatS4.png?updatedAt=1709298834522"
alt="Plane Analytics"
width="100%"
/>
@@ -118,7 +161,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_pages_dark_mode.webp"
src="https://ik.imagekit.io/w2okwbtu2/Analytics_0o22gLRtp.png?updatedAt=1709298834389"
alt="Plane Pages"
width="100%"
/>
@@ -128,7 +171,7 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_commad_k_dark_mode.webp"
src="https://ik.imagekit.io/w2okwbtu2/Drive_LlfeY4xn3.png?updatedAt=1709298837917"
alt="Plane Command Menu"
width="100%"
/>
@@ -136,20 +179,22 @@ For self hosting environment setup, visit the [Self Hosting](https://docs.plane.
</p>
</p>
## 📚Documentation
For full documentation, visit [docs.plane.so](https://docs.plane.so/)
To see how to Contribute, visit [here](https://github.com/makeplane/plane/blob/master/CONTRIBUTING.md).
## ❤️ Community
The Plane community can be found on GitHub Discussions, where you can ask questions, voice ideas, and share your projects.
To chat with other community members you can join the [Plane Discord](https://discord.com/invite/A92xrEGCge).
Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community channels.
## ⛓️ Security
If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email engineering@plane.so to disclose any security vulnerabilities.
If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports.
Email squawk@plane.so to disclose any security vulnerabilities.
## ❤️ Contribute
There are many ways to contribute to Plane, including:
- Submitting [bugs](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%F0%9F%90%9Bbug&projects=&template=--bug-report.yaml&title=%5Bbug%5D%3A+) and [feature requests](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%E2%9C%A8feature&projects=&template=--feature-request.yaml&title=%5Bfeature%5D%3A+) for various components.
- Reviewing [the documentation](https://docs.plane.so/) and submitting [pull requests](https://github.com/makeplane/plane), from fixing typos to adding new features.
- Speaking or writing about Plane or any other ecosystem integration and [letting us know](https://discord.com/invite/A92xrEGCge)!
- Upvoting [popular feature requests](https://github.com/makeplane/plane/issues) to show your support.
### We couldn't have done this without you.
<a href="https://github.com/makeplane/plane/graphs/contributors">
<img src="https://contrib.rocks/image?repo=makeplane/plane" />
</a>
+2 -2
View File
@@ -716,9 +716,9 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
# Validation check if the issue already exists
if (
str(request.data.get("external_id"))
request.data.get("external_id")
and (issue_comment.external_id != str(request.data.get("external_id")))
and Issue.objects.filter(
and IssueComment.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get(
+1
View File
@@ -58,6 +58,7 @@ def dashboard_overview_stats(self, request, slug):
pending_issues_count = Issue.issue_objects.filter(
~Q(state__group__in=["completed", "cancelled"]),
target_date__lt=timezone.now().date(),
project__project_projectmember__is_active=True,
project__project_projectmember__member=request.user,
workspace__slug=slug,
@@ -40,9 +40,11 @@ export const LinkEditView = ({
const [positionRef, setPositionRef] = useState({ from: from, to: to });
const [localUrl, setLocalUrl] = useState(viewProps.url);
const linkRemoved = useRef<Boolean>();
const linkRemoved = useRef<boolean>();
const getText = (from: number, to: number) => {
if (to >= editor.state.doc.content.size) return "";
const text = editor.state.doc.textBetween(from, to, "\n");
return text;
};
@@ -72,10 +74,12 @@ export const LinkEditView = ({
const url = isValidUrl(localUrl) ? localUrl : viewProps.url;
if (to >= editor.state.doc.content.size) return;
editor.view.dispatch(editor.state.tr.removeMark(from, to, editor.schema.marks.link));
editor.view.dispatch(editor.state.tr.addMark(from, to, editor.schema.marks.link.create({ href: url })));
},
[localUrl]
[localUrl, editor, from, to, viewProps.url]
);
const handleUpdateText = (text: string) => {
+2
View File
@@ -203,6 +203,8 @@ export interface ViewFlags {
export type GroupByColumnTypes =
| "project"
| "cycle"
| "module"
| "state"
| "state_detail.group"
| "priority"
+6
View File
@@ -14,6 +14,8 @@ export type TIssueGroupByOptions =
| "project"
| "assignees"
| "mentions"
| "cycle"
| "module"
| null;
export type TIssueOrderByOptions =
@@ -60,6 +62,8 @@ export type TIssueParams =
| "created_by"
| "subscriber"
| "labels"
| "cycle"
| "module"
| "start_date"
| "target_date"
| "project"
@@ -79,6 +83,8 @@ export interface IIssueFilterOptions {
labels?: string[] | null;
priority?: string[] | null;
project?: string[] | null;
cycle?: string[] | null;
module?: string[] | null;
start_date?: string[] | null;
state?: string[] | null;
state_group?: string[] | null;
+2 -1
View File
@@ -27,6 +27,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
noBorder = false,
noChevron = false,
optionsClassName = "",
menuItemsClassName = "",
verticalEllipsis = false,
portalElement,
menuButtonOnClick,
@@ -70,7 +71,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
useOutsideClickDetector(dropdownRef, closeDropdown);
let menuItems = (
<Menu.Items className="fixed z-10" static>
<Menu.Items className={cn("fixed z-10", menuItemsClassName)} static>
<div
className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",
+1
View File
@@ -24,6 +24,7 @@ export interface ICustomMenuDropdownProps extends IDropdownProps {
noBorder?: boolean;
verticalEllipsis?: boolean;
menuButtonOnClick?: (...args: any) => void;
menuItemsClassName?: string;
onMenuClose?: () => void;
closeOnSelect?: boolean;
portalElement?: Element | null;
@@ -5,17 +5,17 @@ import { observer } from "mobx-react";
type Props = {
onClick?: () => void;
}
};
export const SidebarHamburgerToggle: FC<Props> = observer((props) => {
const { onClick } = props
const { onClick } = props;
const { theme: themeStore } = useApplication();
return (
<div
className="w-7 h-7 flex-shrink-0 rounded flex justify-center items-center bg-custom-background-80 transition-all hover:bg-custom-background-90 cursor-pointer group md:hidden"
onClick={() => {
if (onClick) onClick()
else themeStore.toggleMobileSidebar()
if (onClick) onClick();
else themeStore.toggleSidebar();
}}
>
<Menu size={14} className="text-custom-text-200 group-hover:text-custom-text-100 transition-all" />
@@ -152,6 +152,7 @@ export const CycleMobileHeader = () => {
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["cycle"]}
/>
</FiltersDropdown>
</div>
@@ -37,7 +37,7 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
key: "overdue",
title: "Issues overdue",
count: widgetStats?.pending_issues_count,
link: `/${workspaceSlug}/workspace-views/assigned/?target_date=${today};before`,
link: `/${workspaceSlug}/workspace-views/assigned/?state_group=backlog,unstarted,started&target_date=${today};before`,
},
{
key: "created",
+1
View File
@@ -67,6 +67,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
const toggleDropdown = () => {
setIsOpen((prevIsOpen) => !prevIsOpen);
if (isOpen) onClose && onClose();
};
const dropdownOnChange = (val: string | null) => {
@@ -67,6 +67,7 @@ export const MemberDropdown: React.FC<Props> = observer((props) => {
const toggleDropdown = () => {
setIsOpen((prevIsOpen) => !prevIsOpen);
if (isOpen) onClose && onClose();
};
const dropdownOnChange = (val: string & string[]) => {
+2 -2
View File
@@ -58,7 +58,7 @@ const BorderButton = (props: ButtonProps) => {
high: "bg-orange-500/20 text-orange-950 border-orange-500",
medium: "bg-yellow-500/20 text-yellow-950 border-yellow-500",
low: "bg-custom-primary-100/20 text-custom-primary-950 border-custom-primary-100",
none: "bg-custom-background-80 border-custom-border-300",
none: "hover:bg-custom-background-80 border-custom-border-300",
};
return (
@@ -197,7 +197,7 @@ const TransparentButton = (props: ButtonProps) => {
high: "text-orange-950",
medium: "text-yellow-950",
low: "text-blue-950",
none: "",
none: "hover:text-custom-text-300",
};
return (
+1 -1
View File
@@ -53,7 +53,7 @@ const EmojiIconPicker: React.FC<Props> = (props) => {
<Popover.Button
ref={buttonRef}
onClick={() => setIsOpen((prev) => !prev)}
className="outline-none"
className="outline-none flex items-center justify-center"
disabled={disabled}
>
{label}
+1
View File
@@ -244,6 +244,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["cycle"]}
/>
</FiltersDropdown>
+1
View File
@@ -248,6 +248,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["module"]}
/>
</FiltersDropdown>
</div>
@@ -131,6 +131,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
const handleInboxIssueNavigation = useCallback(
(direction: "next" | "prev") => {
if (!inboxIssues || !inboxIssueId) return;
const activeElement = document.activeElement as HTMLElement;
if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return;
const nextIssueIndex =
direction === "next"
? (currentIssueIndex + 1) % inboxIssues.length
@@ -50,7 +50,7 @@ export const IssueCycleSelect: React.FC<TIssueCycleSelect> = observer((props) =>
buttonVariant="transparent-with-text"
className="w-full group"
buttonContainerClassName="w-full text-left"
buttonClassName={`text-sm ${issue?.cycle_id ? "" : "text-custom-text-400"}`}
buttonClassName={`text-sm justify-between ${issue?.cycle_id ? "" : "text-custom-text-400"}`}
placeholder="No cycle"
hideIcon
dropdownArrow
@@ -74,7 +74,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
return (
<div
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey && !isEmpty) {
if (e.key === "Enter" && !e.shiftKey && !isEmpty && !isSubmitting) {
handleSubmit(onSubmit)(e);
}
}}
@@ -11,7 +11,8 @@ import {
XCircle,
CircleDot,
CopyPlus,
CalendarDays,
CalendarClock,
CalendarCheck2,
} from "lucide-react";
// hooks
import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store";
@@ -236,7 +237,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-2 h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CalendarDays className="h-4 w-4 flex-shrink-0" />
<CalendarClock className="h-4 w-4 flex-shrink-0" />
<span>Start date</span>
</div>
<DateDropdown
@@ -262,7 +263,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-2 h-8">
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
<CalendarDays className="h-4 w-4 flex-shrink-0" />
<CalendarCheck2 className="h-4 w-4 flex-shrink-0" />
<span>Due date</span>
</div>
<DateDropdown
@@ -0,0 +1,48 @@
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
// hooks
import { useCycle } from "hooks/store";
// ui
import { CycleGroupIcon } from "@plane/ui";
// types
import { TCycleGroups } from "@plane/types";
type Props = {
handleRemove: (val: string) => void;
values: string[];
editable: boolean | undefined;
};
export const AppliedCycleFilters: React.FC<Props> = observer((props) => {
const { handleRemove, values, editable } = props;
// store hooks
const { getCycleById } = useCycle();
return (
<>
{values.map((cycleId) => {
const cycleDetails = getCycleById(cycleId) ?? null;
if (!cycleDetails) return null;
const cycleStatus = (cycleDetails?.status ? cycleDetails?.status.toLocaleLowerCase() : "draft") as TCycleGroups;
return (
<div key={cycleId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3 w-3 flex-shrink-0" />
<span className="normal-case">{cycleDetails.name}</span>
{editable && (
<button
type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
onClick={() => handleRemove(cycleId)}
>
<X size={10} strokeWidth={2} />
</button>
)}
</div>
);
})}
</>
);
});
@@ -1,12 +1,15 @@
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
import { useRouter } from "next/router";
// hooks
import { useUser } from "hooks/store";
import { useApplication, useUser } from "hooks/store";
// components
import {
AppliedCycleFilters,
AppliedDateFilters,
AppliedLabelsFilters,
AppliedMembersFilters,
AppliedModuleFilters,
AppliedPriorityFilters,
AppliedProjectFilters,
AppliedStateFilters,
@@ -34,6 +37,9 @@ const dateFilters = ["start_date", "target_date"];
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props;
// store hooks
const {
router: { moduleId, cycleId },
} = useApplication();
const {
membership: { currentProjectRole },
} = useUser();
@@ -104,6 +110,20 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
values={value}
/>
)}
{filterKey === "cycle" && !cycleId && (
<AppliedCycleFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("cycle", val)}
values={value}
/>
)}
{filterKey === "module" && !moduleId && (
<AppliedModuleFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("module", val)}
values={value}
/>
)}
{isEditingAllowed && (
<button
type="button"
@@ -5,5 +5,7 @@ export * from "./label";
export * from "./members";
export * from "./priority";
export * from "./project";
export * from "./module";
export * from "./cycle";
export * from "./state";
export * from "./state-group";
@@ -0,0 +1,44 @@
import { observer } from "mobx-react-lite";
import { X } from "lucide-react";
// hooks
import { useModule } from "hooks/store";
// ui
import { DiceIcon } from "@plane/ui";
type Props = {
handleRemove: (val: string) => void;
values: string[];
editable: boolean | undefined;
};
export const AppliedModuleFilters: React.FC<Props> = observer((props) => {
const { handleRemove, values, editable } = props;
// store hooks
const { getModuleById } = useModule();
return (
<>
{values.map((moduleId) => {
const moduleDetails = getModuleById(moduleId) ?? null;
if (!moduleDetails) return null;
return (
<div key={moduleId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<DiceIcon className="h-3 w-3 flex-shrink-0" />
<span className="normal-case">{moduleDetails.name}</span>
{editable && (
<button
type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
onClick={() => handleRemove(moduleId)}
>
<X size={10} strokeWidth={2} />
</button>
)}
</div>
);
})}
</>
);
});
@@ -11,7 +11,7 @@ import {
FilterSubGroupBy,
} from "components/issues";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueGroupByOptions } from "@plane/types";
import { ILayoutDisplayFiltersOptions } from "constants/issue";
type Props = {
@@ -20,6 +20,7 @@ type Props = {
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
handleDisplayPropertiesUpdate: (updatedDisplayProperties: Partial<IIssueDisplayProperties>) => void;
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
ignoreGroupedFilters?: Partial<TIssueGroupByOptions>[];
};
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
@@ -29,6 +30,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
handleDisplayFiltersUpdate,
handleDisplayPropertiesUpdate,
layoutDisplayFiltersOptions,
ignoreGroupedFilters = [],
} = props;
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
@@ -54,6 +56,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
group_by: val,
})
}
ignoreGroupedFilters={ignoreGroupedFilters}
/>
</div>
)}
@@ -71,6 +74,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
})
}
subGroupByOptions={layoutDisplayFiltersOptions?.display_filters.sub_group_by ?? []}
ignoreGroupedFilters={ignoreGroupedFilters}
/>
</div>
)}
@@ -1,6 +1,5 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// components
import { FilterHeader, FilterOption } from "components/issues";
// types
@@ -12,10 +11,11 @@ type Props = {
displayFilters: IIssueDisplayFilterOptions;
groupByOptions: TIssueGroupByOptions[];
handleUpdate: (val: TIssueGroupByOptions) => void;
ignoreGroupedFilters: Partial<TIssueGroupByOptions>[];
};
export const FilterGroupBy: React.FC<Props> = observer((props) => {
const { displayFilters, groupByOptions, handleUpdate } = props;
const { displayFilters, groupByOptions, handleUpdate, ignoreGroupedFilters } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@@ -34,6 +34,7 @@ export const FilterGroupBy: React.FC<Props> = observer((props) => {
{ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => {
if (displayFilters.layout === "kanban" && selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy)
return null;
if (ignoreGroupedFilters.includes(groupBy?.key)) return null;
return (
<FilterOption
@@ -1,6 +1,5 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
// components
import { FilterHeader, FilterOption } from "components/issues";
// types
@@ -12,10 +11,11 @@ type Props = {
displayFilters: IIssueDisplayFilterOptions;
handleUpdate: (val: TIssueGroupByOptions) => void;
subGroupByOptions: TIssueGroupByOptions[];
ignoreGroupedFilters: Partial<TIssueGroupByOptions>[];
};
export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
const { displayFilters, handleUpdate, subGroupByOptions } = props;
const { displayFilters, handleUpdate, subGroupByOptions, ignoreGroupedFilters } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@@ -33,6 +33,7 @@ export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
<div>
{ISSUE_GROUP_BY_OPTIONS.filter((option) => subGroupByOptions.includes(option.key)).map((subGroupBy) => {
if (selectedGroupBy !== null && subGroupBy.key === selectedGroupBy) return null;
if (ignoreGroupedFilters.includes(subGroupBy?.key)) return null;
return (
<FilterOption
@@ -0,0 +1,96 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import sortBy from "lodash/sortBy";
// components
import { FilterHeader, FilterOption } from "components/issues";
import { useApplication, useCycle } from "hooks/store";
// ui
import { Loader, CycleGroupIcon } from "@plane/ui";
// types
import { TCycleGroups } from "@plane/types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterCycle: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, searchQuery } = props;
// hooks
const {
router: { projectId },
} = useApplication();
const { getCycleById, getProjectCycleIds } = useCycle();
// states
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const cycleIds = projectId ? getProjectCycleIds(projectId) : undefined;
const cycles = cycleIds?.map((projectId) => getCycleById(projectId)!) ?? null;
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = sortBy(
cycles?.filter((cycle) => cycle.name.toLowerCase().includes(searchQuery.toLowerCase())),
(cycle) => cycle.name.toLowerCase()
);
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups;
return (
<>
<FilterHeader
title={`Cycle ${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((cycle) => (
<FilterOption
key={cycle.id}
isChecked={appliedFilters?.includes(cycle.id) ? true : false}
onClick={() => handleUpdate(cycle.id)}
icon={
<CycleGroupIcon cycleGroup={cycleStatus(cycle?.status)} className="h-3.5 w-3.5 flex-shrink-0" />
}
title={cycle.name}
activePulse={cycleStatus(cycle?.status) === "current" ? true : false}
/>
))}
{filteredOptions.length > 5 && (
<button
type="button"
className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs italic text-custom-text-400">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</>
);
});
@@ -1,6 +1,8 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
import { Search, X } from "lucide-react";
// hooks
import { useApplication } from "hooks/store";
// components
import {
FilterAssignees,
@@ -13,6 +15,8 @@ import {
FilterState,
FilterStateGroup,
FilterTargetDate,
FilterCycle,
FilterModule,
} from "components/issues";
// types
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
@@ -30,6 +34,10 @@ type Props = {
export const FilterSelection: React.FC<Props> = observer((props) => {
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
// hooks
const {
router: { moduleId, cycleId },
} = useApplication();
// states
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
@@ -102,6 +110,28 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
</div>
)}
{/* cycle */}
{isFilterEnabled("cycle") && !cycleId && (
<div className="py-2">
<FilterCycle
appliedFilters={filters.cycle ?? null}
handleUpdate={(val) => handleFiltersUpdate("cycle", val)}
searchQuery={filtersSearchQuery}
/>
</div>
)}
{/* module */}
{isFilterEnabled("module") && !moduleId && (
<div className="py-2">
<FilterModule
appliedFilters={filters.module ?? null}
handleUpdate={(val) => handleFiltersUpdate("module", val)}
searchQuery={filtersSearchQuery}
/>
</div>
)}
{/* assignees */}
{isFilterEnabled("mentions") && (
<div className="py-2">
@@ -8,4 +8,6 @@ export * from "./project";
export * from "./start-date";
export * from "./state-group";
export * from "./state";
export * from "./cycle";
export * from "./module";
export * from "./target-date";
@@ -0,0 +1,89 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import sortBy from "lodash/sortBy";
// components
import { FilterHeader, FilterOption } from "components/issues";
import { useApplication, useModule } from "hooks/store";
// ui
import { Loader, DiceIcon } from "@plane/ui";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterModule: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, searchQuery } = props;
// hooks
const {
router: { projectId },
} = useApplication();
const { getModuleById, getProjectModuleIds } = useModule();
// states
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const moduleIds = projectId ? getProjectModuleIds(projectId) : undefined;
const modules = moduleIds?.map((projectId) => getModuleById(projectId)!) ?? null;
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = sortBy(
modules?.filter((module) => module.name.toLowerCase().includes(searchQuery.toLowerCase())),
(module) => module.name.toLowerCase()
);
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
title={`Module ${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div>
{filteredOptions ? (
filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((cycle) => (
<FilterOption
key={cycle.id}
isChecked={appliedFilters?.includes(cycle.id) ? true : false}
onClick={() => handleUpdate(cycle.id)}
icon={<DiceIcon className="h-3 w-3 flex-shrink-0" />}
title={cycle.name}
/>
))}
{filteredOptions.length > 5 && (
<button
type="button"
className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs italic text-custom-text-400">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</>
);
});
@@ -8,10 +8,11 @@ type Props = {
title: React.ReactNode;
onClick?: () => void;
multiple?: boolean;
activePulse?: boolean;
};
export const FilterOption: React.FC<Props> = (props) => {
const { icon, isChecked, multiple = true, onClick, title } = props;
const { icon, isChecked, multiple = true, onClick, title, activePulse = false } = props;
return (
<button
@@ -30,6 +31,9 @@ export const FilterOption: React.FC<Props> = (props) => {
{icon && <div className="grid w-5 flex-shrink-0 place-items-center">{icon}</div>}
<div className="flex-grow truncate text-xs text-custom-text-200">{title}</div>
</div>
{activePulse && (
<div className="flex-shrink-0 text-xs w-2 h-2 rounded-full bg-custom-primary-100 animate-pulse ml-auto" />
)}
</button>
);
};
@@ -291,27 +291,29 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
</Droppable>
</div>
<KanBanView
issuesMap={issueMap}
issueIds={issueIds}
displayProperties={displayProperties}
sub_group_by={sub_group_by}
group_by={group_by}
handleIssues={handleIssues}
quickActions={renderQuickActions}
handleKanbanFilters={handleKanbanFilters}
kanbanFilters={kanbanFilters}
enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
quickAddCallback={issues?.quickAddIssue}
viewId={viewId}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
canEditProperties={canEditProperties}
storeType={storeType}
addIssuesToView={addIssuesToView}
scrollableContainerRef={scrollableContainerRef}
isDragStarted={isDragStarted}
/>
<div className="w-max h-max">
<KanBanView
issuesMap={issueMap}
issueIds={issueIds}
displayProperties={displayProperties}
sub_group_by={sub_group_by}
group_by={group_by}
handleIssues={handleIssues}
quickActions={renderQuickActions}
handleKanbanFilters={handleKanbanFilters}
kanbanFilters={kanbanFilters}
enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
quickAddCallback={issues?.quickAddIssue}
viewId={viewId}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
canEditProperties={canEditProperties}
storeType={storeType}
addIssuesToView={addIssuesToView}
scrollableContainerRef={scrollableContainerRef}
isDragStarted={isDragStarted}
/>
</div>
</DragDropContext>
</div>
</div>
@@ -141,7 +141,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
>
<div
className={cn(
"rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400",
"rounded border-[0.5px] w-full border-custom-border-200 bg-custom-background-100 text-sm transition-all hover:border-custom-border-400",
{ "hover:cursor-grab": !isDragDisabled },
{ "border-custom-primary-100": snapshot.isDragging },
{ "border border-custom-primary-70 hover:border-custom-primary-70": peekIssueId === issue.id }
@@ -1,6 +1,15 @@
import { observer } from "mobx-react-lite";
// hooks
import { useIssueDetail, useKanbanView, useLabel, useMember, useProject, useProjectState } from "hooks/store";
import {
useCycle,
useIssueDetail,
useKanbanView,
useLabel,
useMember,
useModule,
useProject,
useProjectState,
} from "hooks/store";
// components
import { HeaderGroupByCard } from "./headers/group-by-card";
import { KanbanGroup } from "./kanban-group";
@@ -79,31 +88,40 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
const member = useMember();
const project = useProject();
const label = useLabel();
const cycle = useCycle();
const _module = useModule();
const projectState = useProjectState();
const { peekIssue } = useIssueDetail();
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member);
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, cycle, _module, label, projectState, member);
if (!list) return null;
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)[_list.id]?.length > 0);
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)?.[_list.id]?.length > 0);
const groupList = showEmptyGroup ? list : groupWithIssues;
const visibilityGroupBy = (_list: IGroupByColumn) =>
sub_group_by ? false : kanbanFilters?.group_by.includes(_list.id) ? true : false;
const visibilityGroupBy = (_list: IGroupByColumn) => {
if (sub_group_by) {
if (kanbanFilters?.sub_group_by.includes(_list.id)) return true;
return false;
} else {
if (kanbanFilters?.group_by.includes(_list.id)) return true;
return false;
}
};
const isGroupByCreatedBy = group_by === "created_by";
return (
<div className={`relative w-full flex gap-3 ${sub_group_by ? "h-full" : "h-full"}`}>
<div className={`relative w-full flex gap-2 ${sub_group_by ? "h-full" : "h-full"}`}>
{groupList &&
groupList.length > 0 &&
groupList.map((_list: IGroupByColumn) => {
const groupByVisibilityToggle = visibilityGroupBy(_list);
return (
<div className={`relative flex flex-shrink-0 flex-col group ${groupByVisibilityToggle ? `` : `w-[340px]`}`}>
<div className={`relative flex flex-shrink-0 flex-col group ${groupByVisibilityToggle ? `` : `w-[350px]`}`}>
{sub_group_by === null && (
<div className="flex-shrink-0 sticky top-0 z-[2] w-full bg-custom-background-90 py-1">
<HeaderGroupByCard
@@ -106,13 +106,21 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
{icon ? icon : <Circle width={14} strokeWidth={2} />}
</div>
<div className={`flex items-center gap-1 ${verticalAlignPosition ? `flex-col` : `w-full flex-row`}`}>
<div
className={`relative overflow-hidden flex items-center gap-1 ${
verticalAlignPosition ? `flex-col` : `w-full flex-row`
}`}
>
<div
className={`line-clamp-1 font-medium text-custom-text-100 ${verticalAlignPosition ? `vertical-lr` : ``}`}
className={`inline-block truncate line-clamp-1 font-medium text-custom-text-100 overflow-hidden ${
verticalAlignPosition ? `vertical-lr max-h-[400px]` : ``
}`}
>
{title}
</div>
<div className={`text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}>
<div
className={`flex-shrink-0 text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}
>
{count || 0}
</div>
</div>
@@ -80,6 +80,10 @@ export const KanbanGroup = (props: IKanbanGroup) => {
preloadedData = { ...preloadedData, state_id: groupValue };
} else if (groupByKey === "priority") {
preloadedData = { ...preloadedData, priority: groupValue };
} else if (groupByKey === "cycle") {
preloadedData = { ...preloadedData, cycle_id: groupValue };
} else if (groupByKey === "module") {
preloadedData = { ...preloadedData, module_ids: [groupValue] };
} else if (groupByKey === "labels" && groupValue != "None") {
preloadedData = { ...preloadedData, label_ids: [groupValue] };
} else if (groupByKey === "assignees" && groupValue != "None") {
@@ -96,6 +100,10 @@ export const KanbanGroup = (props: IKanbanGroup) => {
preloadedData = { ...preloadedData, state_id: subGroupValue };
} else if (subGroupByKey === "priority") {
preloadedData = { ...preloadedData, priority: subGroupValue };
} else if (groupByKey === "cycle") {
preloadedData = { ...preloadedData, cycle_id: subGroupValue };
} else if (groupByKey === "module") {
preloadedData = { ...preloadedData, module_ids: [subGroupValue] };
} else if (subGroupByKey === "labels" && subGroupValue != "None") {
preloadedData = { ...preloadedData, label_ids: [subGroupValue] };
} else if (subGroupByKey === "assignees" && subGroupValue != "None") {
@@ -115,9 +123,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
{(provided: any, snapshot: any) => (
<div
className={`relative h-full w-full transition-all ${
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
}`}
className={`relative h-full transition-all ${snapshot.isDraggingOver ? `bg-custom-background-80` : ``}`}
{...provided.droppableProps}
ref={provided.innerRef}
>
@@ -18,7 +18,7 @@ import {
} from "@plane/types";
// constants
import { EIssueActions } from "../types";
import { useLabel, useMember, useProject, useProjectState } from "hooks/store";
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
import { getGroupByColumns } from "../utils";
import { TCreateModalStoreTypes } from "constants/issue";
@@ -30,6 +30,15 @@ interface ISubGroupSwimlaneHeader {
kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
}
const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => {
let headerCount = 0;
Object.keys(issueIds).map((groupState) => {
headerCount = headerCount + (issueIds?.[groupState]?.[groupById]?.length || 0);
});
return headerCount;
};
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issueIds,
sub_group_by,
@@ -38,18 +47,18 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
kanbanFilters,
handleKanbanFilters,
}) => (
<div className="relative flex h-max min-h-full w-full items-center">
<div className="relative flex gap-2 h-max min-h-full w-full items-center">
{list &&
list.length > 0 &&
list.map((_list: IGroupByColumn) => (
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[340px] flex-shrink-0 flex-col">
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
<HeaderGroupByCard
sub_group_by={sub_group_by}
group_by={group_by}
column_id={_list.id}
icon={_list.icon}
title={_list.name}
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0}
count={getSubGroupHeaderIssuesCount(issueIds as TSubGroupedIssues, _list?.id)}
kanbanFilters={kanbanFilters}
handleKanbanFilters={handleKanbanFilters}
issuePayload={_list.payload}
@@ -217,10 +226,28 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
const member = useMember();
const project = useProject();
const label = useLabel();
const cycle = useCycle();
const _module = useModule();
const projectState = useProjectState();
const groupByList = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member);
const subGroupByList = getGroupByColumns(sub_group_by as GroupByColumnTypes, project, label, projectState, member);
const groupByList = getGroupByColumns(
group_by as GroupByColumnTypes,
project,
cycle,
_module,
label,
projectState,
member
);
const subGroupByList = getGroupByColumns(
sub_group_by as GroupByColumnTypes,
project,
cycle,
_module,
label,
projectState,
member
);
if (!groupByList || !subGroupByList) return null;
@@ -3,7 +3,7 @@ import { useRef } from "react";
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
import { HeaderGroupByCard } from "./headers/group-by-card";
// hooks
import { useLabel, useMember, useProject, useProjectState } from "hooks/store";
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
// types
import {
GroupByColumnTypes,
@@ -65,10 +65,21 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
const project = useProject();
const label = useLabel();
const projectState = useProjectState();
const cycle = useCycle();
const _module = useModule();
const containerRef = useRef<HTMLDivElement | null>(null);
const groups = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member, true);
const groups = getGroupByColumns(
group_by as GroupByColumnTypes,
project,
cycle,
_module,
label,
projectState,
member,
true
);
if (!groups) return null;
@@ -1,7 +1,7 @@
import { useCallback, useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import { Layers, Link, Paperclip } from "lucide-react";
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
import xor from "lodash/xor";
// hooks
import { useEventTracker, useEstimate, useLabel, useIssues, useProjectState } from "hooks/store";
@@ -286,6 +286,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
onChange={handleStartDate}
maxDate={maxDate ?? undefined}
placeholder="Start date"
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"}
disabled={isReadOnly}
showTooltip
@@ -301,6 +302,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
onChange={handleTargetDate}
minDate={minDate ?? undefined}
placeholder="Due date"
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
buttonClassName={shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group) ? "text-red-500" : ""}
clearIconClassName="!text-custom-text-100"
@@ -96,6 +96,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
storeType={EIssuesStoreType.PROJECT}
/>
<CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start"
customButton={customActionButton}
portalElement={portalElement}
@@ -56,6 +56,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
onSubmit={handleDelete}
/>
<CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start"
customButton={customActionButton}
portalElement={portalElement}
@@ -106,6 +106,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
storeType={EIssuesStoreType.CYCLE}
/>
<CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start"
customButton={customActionButton}
portalElement={portalElement}
@@ -106,6 +106,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
storeType={EIssuesStoreType.MODULE}
/>
<CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start"
customButton={customActionButton}
portalElement={portalElement}
@@ -108,6 +108,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
isDraft={isDraftIssue}
/>
<CustomMenu
menuItemsClassName="z-[14]"
placement="bottom-start"
customButton={customActionButton}
portalElement={portalElement}
@@ -1,5 +1,6 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { CalendarCheck2 } from "lucide-react";
// hooks
import { useProjectState } from "hooks/store";
// components
@@ -43,6 +44,7 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props)
}}
disabled={disabled}
placeholder="Due date"
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
buttonVariant="transparent-with-text"
buttonContainerClassName="w-full"
buttonClassName={cn("rounded-none text-left", {
@@ -1,5 +1,6 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { CalendarClock } from "lucide-react";
// components
import { DateDropdown } from "components/dropdowns";
// helpers
@@ -35,6 +36,7 @@ export const SpreadsheetStartDateColumn: React.FC<Props> = observer((props: Prop
}}
disabled={disabled}
placeholder="Start date"
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
buttonVariant="transparent-with-text"
buttonClassName="rounded-none text-left"
buttonContainerClassName="w-full"
@@ -189,7 +189,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
<td
id={`issue-${issueDetail.id}`}
className={cn(
"sticky group left-0 h-11 w-[28rem] flex items-center bg-custom-background-100 text-sm after:absolute border-r-[0.5px] z-[1] border-custom-border-200",
"sticky group left-0 h-11 w-[28rem] flex items-center bg-custom-background-100 text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200",
{
"border-b-[0.5px]": peekIssue?.issueId !== issueDetail.id,
},
@@ -19,10 +19,10 @@ export const SpreadsheetHeader = (props: Props) => {
const { displayProperties, displayFilters, handleDisplayFilterUpdate, isEstimateEnabled } = props;
return (
<thead className="sticky top-0 left-0 z-[2] border-b-[0.5px] border-custom-border-100">
<thead className="sticky top-0 left-0 z-[12] border-b-[0.5px] border-custom-border-100">
<tr>
<th
className="sticky left-0 z-[2] h-11 w-[28rem] flex items-center bg-custom-background-90 text-sm font-medium before:absolute before:h-full before:right-0 before:border-[0.5px] before:border-custom-border-100"
className="sticky left-0 z-[15] h-11 w-[28rem] flex items-center bg-custom-background-90 text-sm font-medium before:absolute before:h-full before:right-0 before:border-[0.5px] before:border-custom-border-100"
tabIndex={-1}
>
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="key">
+80 -5
View File
@@ -1,16 +1,25 @@
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
import { EIssueListRow, ISSUE_PRIORITIES } from "constants/issue";
import { renderEmoji } from "helpers/emoji.helper";
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
// stores
import { IMemberRootStore } from "store/member";
import { IProjectStore } from "store/project/project.store";
import { IStateStore } from "store/state.store";
import { GroupByColumnTypes, IGroupByColumn, IIssueListRow, TGroupedIssues, TUnGroupedIssues } from "@plane/types";
import { STATE_GROUPS } from "constants/state";
import { ILabelStore } from "store/label.store";
import { ICycleStore } from "store/cycle.store";
import { IModuleStore } from "store/module.store";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
// constants
import { STATE_GROUPS } from "constants/state";
import { ISSUE_PRIORITIES } from "constants/issue";
// types
import { GroupByColumnTypes, IGroupByColumn, TCycleGroups } from "@plane/types";
import { ContrastIcon } from "lucide-react";
export const getGroupByColumns = (
groupBy: GroupByColumnTypes | null,
project: IProjectStore,
cycle: ICycleStore,
module: IModuleStore,
label: ILabelStore,
projectState: IStateStore,
member: IMemberRootStore,
@@ -19,6 +28,10 @@ export const getGroupByColumns = (
switch (groupBy) {
case "project":
return getProjectColumns(project);
case "cycle":
return getCycleColumns(project, cycle);
case "module":
return getModuleColumns(project, module);
case "state":
return getStateColumns(projectState);
case "state_detail.group":
@@ -55,6 +68,68 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
}) as any;
};
const getCycleColumns = (projectStore: IProjectStore, cycleStore: ICycleStore): IGroupByColumn[] | undefined => {
const { currentProjectDetails } = projectStore;
const { getProjectCycleIds, getCycleById } = cycleStore;
if (!currentProjectDetails || !currentProjectDetails?.id) return;
const cycleIds = currentProjectDetails?.id ? getProjectCycleIds(currentProjectDetails?.id) : undefined;
if (!cycleIds) return;
const cycles = [];
cycleIds.map((cycleId) => {
const cycle = getCycleById(cycleId);
if (cycle) {
const cycleStatus = cycle.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
cycles.push({
id: cycle.id,
name: cycle.name,
icon: <CycleGroupIcon cycleGroup={cycleStatus as TCycleGroups} className="h-3.5 w-3.5" />,
payload: { cycle_id: cycle.id },
});
}
});
cycles.push({
id: "None",
name: "None",
icon: <ContrastIcon className="h-3.5 w-3.5" />,
});
return cycles as any;
};
const getModuleColumns = (projectStore: IProjectStore, moduleStore: IModuleStore): IGroupByColumn[] | undefined => {
const { currentProjectDetails } = projectStore;
const { getProjectModuleIds, getModuleById } = moduleStore;
if (!currentProjectDetails || !currentProjectDetails?.id) return;
const moduleIds = currentProjectDetails?.id ? getProjectModuleIds(currentProjectDetails?.id) : undefined;
if (!moduleIds) return;
const modules = [];
moduleIds.map((moduleId) => {
const _module = getModuleById(moduleId);
if (_module)
modules.push({
id: _module.id,
name: _module.name,
icon: <DiceIcon className="w-3.5 h-3.5" />,
payload: { module_ids: [_module.id] },
});
}) as any;
modules.push({
id: "None",
name: "None",
icon: <DiceIcon className="w-3.5 h-3.5" />,
});
return modules as any;
};
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
const { projectStates } = projectState;
if (!projectStates) return;
+2 -1
View File
@@ -100,8 +100,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
const fetchIssueDetail = async (issueId: string | undefined) => {
if (!workspaceSlug) return;
if (!projectId || issueId === undefined) {
setDescription("<p></p>");
setDescription(data?.description_html || "<p></p>");
return;
}
const response = await fetchIssue(workspaceSlug, projectId, issueId, isDraft ? "DRAFT" : "DEFAULT");
+142 -142
View File
@@ -14,153 +14,153 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
import { ProjectAnalyticsModal } from "components/analytics";
export const IssuesMobileHeader = () => {
const layouts = [
{ key: "list", title: "List", icon: List },
{ key: "kanban", title: "Kanban", icon: Kanban },
{ key: "calendar", title: "Calendar", icon: Calendar },
];
const [analyticsModal, setAnalyticsModal] = useState(false);
const { workspaceSlug, projectId } = router.query as {
workspaceSlug: string;
projectId: string;
};
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const layouts = [
{ key: "list", title: "List", icon: List },
{ key: "kanban", title: "Kanban", icon: Kanban },
{ key: "calendar", title: "Calendar", icon: Calendar },
];
const [analyticsModal, setAnalyticsModal] = useState(false);
const { workspaceSlug, projectId } = router.query as {
workspaceSlug: string;
projectId: string;
};
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.PROJECT);
const {
project: { projectMemberIds },
} = useMember();
const activeLayout = issueFilters?.displayFilters?.layout;
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.PROJECT);
const {
project: { projectMemberIds },
} = useMember();
const activeLayout = issueFilters?.displayFilters?.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, projectId, updateFilters]
);
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, projectId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, projectId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, projectId, updateFilters]
);
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
projectDetails={currentProjectDetails ?? undefined}
/>
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm"
placement="bottom-start"
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
{layouts.map((layout, index) => (
<CustomMenu.MenuItem
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Filters"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Filters
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, projectId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, projectId, updateFilters]
);
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
projectDetails={currentProjectDetails ?? undefined}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
/>
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm"
placement="bottom-start"
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
{layouts.map((layout, index) => (
<CustomMenu.MenuItem
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Filters"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Filters
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
/>
</FiltersDropdown>
</div>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Display"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Display
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</div>
</FiltersDropdown>
</div>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Display"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Display
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</div>
<button
onClick={() => setAnalyticsModal(true)}
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
>
Analytics
</button>
</div>
</>
);
<button
onClick={() => setAnalyticsModal(true)}
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
>
Analytics
</button>
</div>
</>
);
};
@@ -1,6 +1,16 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { Signal, Tag, Triangle, LayoutPanelTop, CircleDot, CopyPlus, XCircle, CalendarDays } from "lucide-react";
import {
Signal,
Tag,
Triangle,
LayoutPanelTop,
CircleDot,
CopyPlus,
XCircle,
CalendarClock,
CalendarCheck2,
} from "lucide-react";
// hooks
import { useIssueDetail, useProject, useProjectState } from "hooks/store";
// ui icons
@@ -118,7 +128,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
{/* start date */}
<div className="flex w-full items-center gap-3 h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CalendarDays className="h-4 w-4 flex-shrink-0" />
<CalendarClock className="h-4 w-4 flex-shrink-0" />
<span>Start date</span>
</div>
<DateDropdown
@@ -145,7 +155,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
{/* due date */}
<div className="flex w-full items-center gap-3 h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CalendarDays className="h-4 w-4 flex-shrink-0" />
<CalendarCheck2 className="h-4 w-4 flex-shrink-0" />
<span>Due date</span>
</div>
<DateDropdown
+144 -143
View File
@@ -9,154 +9,155 @@ import router from "next/router";
import { useCallback, useState } from "react";
export const ModuleMobileHeader = () => {
const [analyticsModal, setAnalyticsModal] = useState(false);
const { getModuleById } = useModule();
const layouts = [
{ key: "list", title: "List", icon: List },
{ key: "kanban", title: "Kanban", icon: Kanban },
{ key: "calendar", title: "Calendar", icon: Calendar },
];
const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const [analyticsModal, setAnalyticsModal] = useState(false);
const { getModuleById } = useModule();
const layouts = [
{ key: "list", title: "List", icon: List },
{ key: "kanban", title: "Kanban", icon: Kanban },
{ key: "calendar", title: "Calendar", icon: Calendar },
];
const { workspaceSlug, projectId, moduleId } = router.query as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.MODULE);
const activeLayout = issueFilters?.displayFilters?.layout;
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.MODULE);
const activeLayout = issueFilters?.displayFilters?.layout;
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
},
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
return (
<div className="block md:hidden">
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
moduleDetails={moduleDetails ?? undefined}
/>
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm"
placement="bottom-start"
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
{layouts.map((layout, index) => (
<CustomMenu.MenuItem
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Filters"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Filters
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
},
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
);
return (
<div className="block md:hidden">
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
moduleDetails={moduleDetails ?? undefined}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
/>
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm"
placement="bottom-start"
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
{layouts.map((layout, index) => (
<CustomMenu.MenuItem
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Filters"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Filters
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
/>
</FiltersDropdown>
</div>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Display"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Display
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</div>
<button
onClick={() => setAnalyticsModal(true)}
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
>
Analytics
</button>
</div>
</FiltersDropdown>
</div>
);
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Display"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Display
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["module"]}
/>
</FiltersDropdown>
</div>
<button
onClick={() => setAnalyticsModal(true)}
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
>
Analytics
</button>
</div>
</div>
);
};
-6
View File
@@ -368,12 +368,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
</div>
</div>
{moduleDetails.description && (
<span className="w-full whitespace-normal break-words py-2.5 text-sm leading-5 text-custom-text-200">
{moduleDetails.description}
</span>
)}
<div className="flex flex-col gap-5 pb-6 pt-2.5">
<div className="flex items-center justify-start gap-1">
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
+1 -1
View File
@@ -144,7 +144,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
const handleProjectClick = () => {
if (window.innerWidth < 768) {
themeStore.toggleMobileSidebar();
themeStore.toggleSidebar();
}
};
+18 -15
View File
@@ -10,7 +10,6 @@ import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-reac
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
// assets
import packageJson from "package.json";
import useSize from "hooks/use-window-size";
const helpOptions = [
{
@@ -43,11 +42,10 @@ export interface WorkspaceHelpSectionProps {
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
// store hooks
const {
theme: { sidebarCollapsed, toggleSidebar, toggleMobileSidebar },
theme: { sidebarCollapsed, toggleSidebar },
commandPalette: { toggleShortcutModal },
} = useApplication();
const [windowWidth] = useSize();
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// refs
@@ -60,8 +58,9 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
return (
<>
<div
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${isCollapsed ? "flex-col" : ""
}`}
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
isCollapsed ? "flex-col" : ""
}`}
>
{!isCollapsed && (
<div className="w-1/2 cursor-default rounded-md bg-green-500/10 px-2.5 py-1.5 text-center text-sm font-medium text-green-500 outline-none">
@@ -72,8 +71,9 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
<Tooltip tooltipContent="Shortcuts">
<button
type="button"
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${isCollapsed ? "w-full" : ""
}`}
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
isCollapsed ? "w-full" : ""
}`}
onClick={() => toggleShortcutModal(true)}
>
<Zap className="h-3.5 w-3.5" />
@@ -82,8 +82,9 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
<Tooltip tooltipContent="Help">
<button
type="button"
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${isCollapsed ? "w-full" : ""
}`}
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 ${
isCollapsed ? "w-full" : ""
}`}
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
>
<HelpCircle className="h-3.5 w-3.5" />
@@ -93,7 +94,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
<button
type="button"
className="grid place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:hidden"
onClick={() => windowWidth <= 768 ? toggleMobileSidebar() : toggleSidebar()}
onClick={() => toggleSidebar()}
>
<MoveLeft className="h-3.5 w-3.5" />
</button>
@@ -101,9 +102,10 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
<Tooltip tooltipContent={`${isCollapsed ? "Expand" : "Hide"}`}>
<button
type="button"
className={`hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${isCollapsed ? "w-full" : ""
}`}
onClick={() => windowWidth <= 768 ? toggleMobileSidebar() : toggleSidebar()}
className={`hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${
isCollapsed ? "w-full" : ""
}`}
onClick={() => toggleSidebar()}
>
<MoveLeft className={`h-3.5 w-3.5 duration-300 ${isCollapsed ? "rotate-180" : ""}`} />
</button>
@@ -121,8 +123,9 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observe
leaveTo="transform opacity-0 scale-95"
>
<div
className={`absolute bottom-2 min-w-[10rem] ${isCollapsed ? "left-full" : "-left-[75px]"
} divide-y divide-custom-border-200 whitespace-nowrap rounded bg-custom-background-100 p-1 shadow-custom-shadow-xs`}
className={`absolute bottom-2 min-w-[10rem] ${
isCollapsed ? "left-full" : "-left-[75px]"
} divide-y divide-custom-border-200 whitespace-nowrap rounded bg-custom-background-100 p-1 shadow-custom-shadow-xs`}
ref={helpOptionsRef}
>
<div className="space-y-1 pb-2">
@@ -8,7 +8,7 @@ import { mutate } from "swr";
import { Check, ChevronDown, CircleUserRound, LogOut, Mails, PlusSquare, Settings, UserCircle2 } from "lucide-react";
import { usePopper } from "react-popper";
// hooks
import { useApplication, useEventTracker, useUser, useWorkspace } from "hooks/store";
import { useApplication, useUser, useWorkspace } from "hooks/store";
// hooks
import useToast from "hooks/use-toast";
// ui
@@ -54,9 +54,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
const { workspaceSlug } = router.query;
// store hooks
const {
theme: { sidebarCollapsed, toggleMobileSidebar },
theme: { sidebarCollapsed, toggleSidebar },
} = useApplication();
const { setTrackElement } = useEventTracker();
const { currentUser, updateCurrentUser, isUserInstanceAdmin, signOut } = useUser();
const { currentWorkspace: activeWorkspace, workspaces } = useWorkspace();
// hooks
@@ -98,7 +97,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
};
const handleItemClick = () => {
if (window.innerWidth < 768) {
toggleMobileSidebar();
toggleSidebar();
}
};
const workspacesList = Object.values(workspaces ?? {});
+6 -5
View File
@@ -31,7 +31,7 @@ export const WorkspaceSidebarMenu = observer(() => {
const handleLinkClick = (itemKey: string) => {
if (window.innerWidth < 768) {
themeStore.toggleMobileSidebar();
themeStore.toggleSidebar();
}
captureEvent(SIDEBAR_CLICKED, {
destination: itemKey,
@@ -52,10 +52,11 @@ export const WorkspaceSidebarMenu = observer(() => {
disabled={!themeStore?.sidebarCollapsed}
>
<div
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${link.highlight(router.asPath, `/${workspaceSlug}`)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
link.highlight(router.asPath, `/${workspaceSlug}`)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
>
{
<link.Icon
@@ -58,6 +58,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
const draftIssues = storedValue ?? {};
if (workspaceSlug && draftIssues[workspaceSlug]) delete draftIssues[workspaceSlug];
setValue(draftIssues);
return Promise.resolve();
};
return (
@@ -66,7 +67,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
isOpen={isDraftIssueModalOpen}
onClose={() => setIsDraftIssueModalOpen(false)}
data={workspaceDraftIssue ?? {}}
// storeType={storeType}
onSubmit={() => removeWorkspaceDraftIssue()}
isDraft={true}
/>
+80 -105
View File
@@ -49,22 +49,6 @@ export const ISSUE_PRIORITIES: {
{ key: "none", title: "None" },
];
export const ISSUE_START_DATE_OPTIONS = [
{ key: "last_week", title: "Last Week" },
{ key: "2_weeks_from_now", title: "2 weeks from now" },
{ key: "1_month_from_now", title: "1 month from now" },
{ key: "2_months_from_now", title: "2 months from now" },
{ key: "custom", title: "Custom" },
];
export const ISSUE_DUE_DATE_OPTIONS = [
{ key: "last_week", title: "Last Week" },
{ key: "2_weeks_from_now", title: "2 weeks from now" },
{ key: "1_month_from_now", title: "1 month from now" },
{ key: "2_months_from_now", title: "2 months from now" },
{ key: "custom", title: "Custom" },
];
export const ISSUE_GROUP_BY_OPTIONS: {
key: TIssueGroupByOptions;
title: string;
@@ -73,6 +57,8 @@ export const ISSUE_GROUP_BY_OPTIONS: {
{ key: "state_detail.group", title: "State Groups" },
{ key: "priority", title: "Priority" },
{ key: "project", title: "Project" }, // required this on my issues
{ key: "cycle", title: "Cycle" }, // required this on my issues
{ key: "module", title: "Module" }, // required this on my issues
{ key: "labels", title: "Labels" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
@@ -140,81 +126,6 @@ export const ISSUE_LAYOUTS: {
{ key: "gantt_chart", title: "Gantt Chart Layout", icon: GanttChartSquare },
];
export const ISSUE_LIST_FILTERS = [
{ key: "mentions", title: "Mentions" },
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
{ key: "labels", title: "Labels" },
{ key: "start_date", title: "Start Date" },
{ key: "due_date", title: "Due Date" },
];
export const ISSUE_KANBAN_FILTERS = [
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
{ key: "labels", title: "Labels" },
{ key: "start_date", title: "Start Date" },
{ key: "due_date", title: "Due Date" },
];
export const ISSUE_CALENDER_FILTERS = [
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
{ key: "labels", title: "Labels" },
];
export const ISSUE_SPREADSHEET_FILTERS = [
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
{ key: "labels", title: "Labels" },
{ key: "start_date", title: "Start Date" },
{ key: "due_date", title: "Due Date" },
];
export const ISSUE_GANTT_FILTERS = [
{ key: "priority", title: "Priority" },
{ key: "state", title: "State" },
{ key: "assignees", title: "Assignees" },
{ key: "created_by", title: "Created By" },
{ key: "labels", title: "Labels" },
{ key: "start_date", title: "Start Date" },
{ key: "due_date", title: "Due Date" },
];
export const ISSUE_LIST_DISPLAY_FILTERS = [
{ key: "group_by", title: "Group By" },
{ key: "order_by", title: "Order By" },
{ key: "issue_type", title: "Issue Type" },
{ key: "sub_issue", title: "Sub Issue" },
{ key: "show_empty_groups", title: "Show Empty Groups" },
];
export const ISSUE_KANBAN_DISPLAY_FILTERS = [
{ key: "group_by", title: "Group By" },
{ key: "order_by", title: "Order By" },
{ key: "issue_type", title: "Issue Type" },
{ key: "sub_issue", title: "Sub Issue" },
{ key: "show_empty_groups", title: "Show Empty Groups" },
];
export const ISSUE_CALENDER_DISPLAY_FILTERS = [{ key: "issue_type", title: "Issue Type" }];
export const ISSUE_SPREADSHEET_DISPLAY_FILTERS = [{ key: "issue_type", title: "Issue Type" }];
export const ISSUE_GANTT_DISPLAY_FILTERS = [
{ key: "order_by", title: "Order By" },
{ key: "issue_type", title: "Issue Type" },
{ key: "sub_issue", title: "Sub Issue" },
];
export interface ILayoutDisplayFiltersOptions {
filters: (keyof IIssueFilterOptions)[];
display_properties: boolean;
@@ -263,10 +174,30 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
archived_issues: {
list: {
filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"],
filters: [
"priority",
"state",
"cycle",
"module",
"assignees",
"created_by",
"labels",
"start_date",
"target_date",
],
display_properties: true,
display_filters: {
group_by: ["state", "state_detail.group", "priority", "labels", "assignees", "created_by", null],
group_by: [
"state",
"cycle",
"module",
"state_detail.group",
"priority",
"labels",
"assignees",
"created_by",
null,
],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
@@ -278,10 +209,10 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
draft_issues: {
list: {
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date"],
display_properties: true,
display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels", null],
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
@@ -291,10 +222,10 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
},
kanban: {
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date"],
display_properties: true,
display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels"],
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels"],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
@@ -350,10 +281,21 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
issues: {
list: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
filters: [
"priority",
"state",
"cycle",
"module",
"assignees",
"mentions",
"created_by",
"labels",
"start_date",
"target_date",
],
display_properties: true,
display_filters: {
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"],
},
@@ -363,11 +305,22 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
},
kanban: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
filters: [
"priority",
"state",
"cycle",
"module",
"assignees",
"mentions",
"created_by",
"labels",
"start_date",
"target_date",
],
display_properties: true,
display_filters: {
group_by: ["state", "priority", "labels", "assignees", "created_by"],
sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null],
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by"],
sub_group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
type: [null, "active", "backlog"],
},
@@ -377,7 +330,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
},
calendar: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date"],
filters: ["priority", "state", "cycle", "module", "assignees", "mentions", "created_by", "labels", "start_date"],
display_properties: true,
display_filters: {
type: [null, "active", "backlog"],
@@ -388,7 +341,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
},
spreadsheet: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
filters: [
"priority",
"state",
"cycle",
"module",
"assignees",
"mentions",
"created_by",
"labels",
"start_date",
"target_date",
],
display_properties: true,
display_filters: {
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
@@ -400,7 +364,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
},
},
gantt_chart: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
filters: [
"priority",
"state",
"cycle",
"module",
"assignees",
"mentions",
"created_by",
"labels",
"start_date",
"target_date",
],
display_properties: false,
display_filters: {
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
+3 -3
View File
@@ -1,6 +1,6 @@
import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types";
import { LayersIcon, DoubleCircleIcon, UserGroupIcon, DiceIcon, ContrastIcon } from "@plane/ui";
import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip } from "lucide-react";
import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip, CalendarCheck2, CalendarClock } from "lucide-react";
import { FC } from "react";
import { ISvgIcons } from "@plane/ui/src/icons/type";
import {
@@ -60,7 +60,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
ascendingOrderTitle: "New",
descendingOrderKey: "target_date",
descendingOrderTitle: "Old",
icon: CalendarDays,
icon: CalendarCheck2,
Column: SpreadsheetDueDateColumn,
},
estimate: {
@@ -114,7 +114,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
ascendingOrderTitle: "New",
descendingOrderKey: "start_date",
descendingOrderTitle: "Old",
icon: CalendarDays,
icon: CalendarClock,
Column: SpreadsheetStartDateColumn,
},
state: {
+8 -11
View File
@@ -30,7 +30,7 @@ export const renderEmoji = (
if (typeof emoji === "object")
return (
<span style={{ color: emoji.color }} className="material-symbols-rounded !text-sm">
<span style={{ fontSize: "16px", color: emoji.color }} className="material-symbols-rounded">
{emoji.name}
</span>
);
@@ -41,16 +41,13 @@ export const groupReactions: (reactions: any[], key: string) => { [key: string]:
reactions: any,
key: string
) => {
const groupedReactions = reactions.reduce(
(acc: any, reaction: any) => {
if (!acc[reaction[key]]) {
acc[reaction[key]] = [];
}
acc[reaction[key]].push(reaction);
return acc;
},
{} as { [key: string]: any[] }
);
const groupedReactions = reactions.reduce((acc: any, reaction: any) => {
if (!acc[reaction[key]]) {
acc[reaction[key]] = [];
}
acc[reaction[key]].push(reaction);
return acc;
}, {} as { [key: string]: any[] });
return groupedReactions;
};
+7 -7
View File
@@ -9,13 +9,13 @@ export const usePage = (pageId: string) => {
const { projectPageMap, projectArchivedPageMap } = context.projectPages;
const { projectId, workspaceSlug } = context.app.router;
if (!projectId || !workspaceSlug) throw new Error("usePage must be used within ProjectProvider");
if (projectPageMap[projectId] && projectPageMap[projectId][pageId]) {
return projectPageMap[projectId][pageId];
} else if (projectArchivedPageMap[projectId] && projectArchivedPageMap[projectId][pageId]) {
return projectArchivedPageMap[projectId][pageId];
} else {
if (!projectId || !workspaceSlug) {
console.log("usePage must be used within ProjectProvider");
return;
}
if (projectPageMap[projectId] && projectPageMap[projectId][pageId]) return projectPageMap[projectId][pageId];
else if (projectArchivedPageMap[projectId] && projectArchivedPageMap[projectId][pageId])
return projectArchivedPageMap[projectId][pageId];
else return;
};
-21
View File
@@ -6,11 +6,6 @@ import { CommandPalette } from "components/command-palette";
import { AppSidebar } from "./sidebar";
import { observer } from "mobx-react-lite";
// FIXME: remove this later
import { useIssues } from "hooks/store/use-issues";
import { EIssuesStoreType } from "constants/issue";
import useSWR from "swr";
export interface IAppLayout {
children: ReactNode;
header: ReactNode;
@@ -20,22 +15,6 @@ export interface IAppLayout {
export const AppLayout: FC<IAppLayout> = observer((props) => {
const { children, header, withProjectWrapper = false } = props;
const workspaceSlug = "plane-demo";
const projectId = "b16907a9-a55f-4f5b-b05e-7065a0869ba6";
const { issues, issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
useSWR(
workspaceSlug && projectId ? `PROJECT_ARCHIVED_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
async () => {
if (workspaceSlug && projectId) {
await issuesFilter?.fetchFilters(workspaceSlug, projectId);
// await issues?.fetchIssues(workspaceSlug, projectId, issues?.groupedIssueIds ? "mutation" : "init-loader");
}
},
{ revalidateIfStale: false, revalidateOnFocus: false }
);
return (
<>
<CommandPalette />
+4 -4
View File
@@ -20,9 +20,9 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
const ref = useRef<HTMLDivElement>(null);
useOutsideClickDetector(ref, () => {
if (themStore.mobileSidebarCollapsed === false) {
if (themStore.sidebarCollapsed === false) {
if (window.innerWidth < 768) {
themStore.toggleMobileSidebar();
themStore.toggleSidebar();
}
}
});
@@ -31,8 +31,8 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
<div
className={`inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300
fixed md:relative
${themStore.mobileSidebarCollapsed ? "-ml-[280px]" : ""}
sm:${themStore.mobileSidebarCollapsed ? "-ml-[280px]" : ""}
${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
sm:${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
md:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
lg:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
`}
+28 -20
View File
@@ -40,7 +40,7 @@ export const ProfileLayoutSidebar = observer(() => {
const { setToastAlert } = useToast();
// store hooks
const {
theme: { sidebarCollapsed, toggleSidebar, toggleMobileSidebar },
theme: { sidebarCollapsed, toggleSidebar },
} = useApplication();
const { currentUser, currentUserSettings, signOut } = useUser();
const { workspaces } = useWorkspace();
@@ -78,7 +78,7 @@ export const ProfileLayoutSidebar = observer(() => {
const handleItemClick = () => {
if (window.innerWidth < 768) {
toggleMobileSidebar();
toggleSidebar();
}
};
@@ -113,8 +113,9 @@ export const ProfileLayoutSidebar = observer(() => {
<div ref={ref} className="flex h-full w-full flex-col gap-y-4">
<Link href={`/${redirectWorkspaceSlug}`} onClick={handleItemClick}>
<div
className={`flex flex-shrink-0 items-center gap-2 truncate px-4 pt-4 ${sidebarCollapsed ? "justify-center" : ""
}`}
className={`flex flex-shrink-0 items-center gap-2 truncate px-4 pt-4 ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
<span className="grid h-5 w-5 flex-shrink-0 place-items-center">
<ChevronLeft className="h-5 w-5" strokeWidth={1} />
@@ -136,10 +137,11 @@ export const ProfileLayoutSidebar = observer(() => {
<Link key={link.key} href={link.href} className="block w-full" onClick={handleItemClick}>
<Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}>
<div
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${link.highlight(router.pathname)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
} ${sidebarCollapsed ? "justify-center" : ""}`}
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
link.highlight(router.pathname)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
} ${sidebarCollapsed ? "justify-center" : ""}`}
>
{<link.Icon className="h-4 w-4" />}
{!sidebarCollapsed && link.label}
@@ -160,17 +162,20 @@ export const ProfileLayoutSidebar = observer(() => {
<Link
key={workspace.id}
href={`/${workspace.slug}`}
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${sidebarCollapsed ? "justify-center" : `justify-between`
}`}
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${
sidebarCollapsed ? "justify-center" : `justify-between`
}`}
onClick={handleItemClick}
>
<span
className={`flex w-full flex-grow items-center gap-x-2 truncate rounded-md px-3 py-1 hover:bg-custom-sidebar-background-80 ${sidebarCollapsed ? "justify-center" : ""
}`}
className={`flex w-full flex-grow items-center gap-x-2 truncate rounded-md px-3 py-1 hover:bg-custom-sidebar-background-80 ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
<span
className={`relative flex h-6 w-6 flex-shrink-0 items-center justify-center p-2 text-xs uppercase ${!workspace?.logo && "rounded bg-custom-primary-500 text-white"
}`}
className={`relative flex h-6 w-6 flex-shrink-0 items-center justify-center p-2 text-xs uppercase ${
!workspace?.logo && "rounded bg-custom-primary-500 text-white"
}`}
>
{workspace?.logo && workspace.logo !== "" ? (
<img
@@ -195,8 +200,9 @@ export const ProfileLayoutSidebar = observer(() => {
<Link className="block w-full" key={link.key} href={link.href} onClick={handleItemClick}>
<Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}>
<div
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80 ${sidebarCollapsed ? "justify-center" : ""
}`}
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-custom-sidebar-text-200 outline-none hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80 ${
sidebarCollapsed ? "justify-center" : ""
}`}
>
{<link.Icon className="h-4 w-4" />}
{!sidebarCollapsed && link.label}
@@ -208,8 +214,9 @@ export const ProfileLayoutSidebar = observer(() => {
</div>
<div className="flex flex-shrink-0 flex-grow items-end px-6 py-2">
<div
className={`flex w-full ${sidebarCollapsed ? "flex-col justify-center gap-2" : "items-center justify-between gap-2"
}`}
className={`flex w-full ${
sidebarCollapsed ? "flex-col justify-center gap-2" : "items-center justify-between gap-2"
}`}
>
<button
type="button"
@@ -229,8 +236,9 @@ export const ProfileLayoutSidebar = observer(() => {
</button>
<button
type="button"
className={`ml-auto hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${sidebarCollapsed ? "w-full" : ""
}`}
className={`ml-auto hidden place-items-center rounded-md p-1.5 text-custom-text-200 outline-none hover:bg-custom-background-90 hover:text-custom-text-100 md:grid ${
sidebarCollapsed ? "w-full" : ""
}`}
onClick={() => toggleSidebar()}
>
<MoveLeft className={`h-3.5 w-3.5 duration-300 ${sidebarCollapsed ? "rotate-180" : ""}`} />
-18
View File
@@ -6,14 +6,12 @@ import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
export interface IThemeStore {
// observables
theme: string | null;
mobileSidebarCollapsed: boolean | undefined;
sidebarCollapsed: boolean | undefined;
profileSidebarCollapsed: boolean | undefined;
workspaceAnalyticsSidebarCollapsed: boolean | undefined;
issueDetailSidebarCollapsed: boolean | undefined;
// actions
toggleSidebar: (collapsed?: boolean) => void;
toggleMobileSidebar: (collapsed?: boolean) => void;
setTheme: (theme: any) => void;
toggleProfileSidebar: (collapsed?: boolean) => void;
toggleWorkspaceAnalyticsSidebar: (collapsed?: boolean) => void;
@@ -22,7 +20,6 @@ export interface IThemeStore {
export class ThemeStore implements IThemeStore {
// observables
mobileSidebarCollapsed: boolean | undefined = true;
sidebarCollapsed: boolean | undefined = undefined;
theme: string | null = null;
profileSidebarCollapsed: boolean | undefined = undefined;
@@ -34,7 +31,6 @@ export class ThemeStore implements IThemeStore {
constructor(_rootStore: any | null = null) {
makeObservable(this, {
// observable
mobileSidebarCollapsed: observable.ref,
sidebarCollapsed: observable.ref,
theme: observable.ref,
profileSidebarCollapsed: observable.ref,
@@ -42,7 +38,6 @@ export class ThemeStore implements IThemeStore {
issueDetailSidebarCollapsed: observable.ref,
// action
toggleSidebar: action,
toggleMobileSidebar: action,
setTheme: action,
toggleProfileSidebar: action,
toggleWorkspaceAnalyticsSidebar: action,
@@ -66,19 +61,6 @@ export class ThemeStore implements IThemeStore {
localStorage.setItem("app_sidebar_collapsed", this.sidebarCollapsed.toString());
};
/**
* Toggle mobile sidebar collapsed state
* @param collapsed
*/
toggleMobileSidebar = (collapsed?: boolean) => {
if (collapsed === undefined) {
this.mobileSidebarCollapsed = !this.mobileSidebarCollapsed;
} else {
this.mobileSidebarCollapsed = collapsed;
}
localStorage.setItem("mobile_sidebar_collapsed", this.mobileSidebarCollapsed.toString());
};
/**
* Toggle the profile sidebar collapsed state
* @param collapsed
+2
View File
@@ -84,6 +84,8 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
if (!filteredParams) return undefined;
if (filteredParams.includes("cycle")) filteredParams.splice(filteredParams.indexOf("cycle"), 1);
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
userFilters?.filters as IIssueFilterOptions,
userFilters?.displayFilters as IIssueDisplayFilterOptions,
@@ -74,6 +74,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
mentions: filters?.mentions || undefined,
created_by: filters?.created_by || undefined,
labels: filters?.labels || undefined,
cycle: filters?.cycle || undefined,
module: filters?.module || undefined,
start_date: filters?.start_date || undefined,
target_date: filters?.target_date || undefined,
project: filters.project || undefined,
@@ -107,6 +109,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
mentions: filters?.mentions || null,
created_by: filters?.created_by || null,
labels: filters?.labels || null,
cycle: filters?.cycle || null,
module: filters?.module || null,
start_date: filters?.start_date || null,
target_date: filters?.target_date || null,
project: filters?.project || null,
@@ -36,6 +36,8 @@ export type TIssueHelperStore = {
const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
project: "project_id",
cycle: "cycle_id",
module: "module_ids",
state: "state_id",
"state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
priority: "priority",
@@ -157,6 +159,10 @@ export class IssueHelperStore implements TIssueHelperStore {
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {});
case "project":
return Object.keys(this.rootStore?.projectMap || {});
case "cycle":
return Object.keys(this.rootStore?.cycleMap || {});
case "module":
return Object.keys(this.rootStore?.moduleMap || {});
default:
return [];
}
+2
View File
@@ -84,6 +84,8 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
if (!filteredParams) return undefined;
if (filteredParams.includes("module")) filteredParams.splice(filteredParams.indexOf("module"), 1);
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
userFilters?.filters as IIssueFilterOptions,
userFilters?.displayFilters as IIssueDisplayFilterOptions,
+1 -1
View File
@@ -148,7 +148,7 @@ export class ProjectStore implements IProjectStore {
projects = sortBy(projects, "created_at");
const projectIds = projects
.filter((project) => project.workspace === currentWorkspace.id && project.is_favorite)
.filter((project) => project.workspace === currentWorkspace.id && project.is_member && project.is_favorite)
.map((project) => project.id);
return projectIds;
}