Compare commits

..
Author SHA1 Message Date
sriram veeraghantaandGitHub 2bc05cc7b7 Merge pull request #2157 from makeplane/stage-release
release: self hosting fixes
2023-09-12 21:50:34 +05:30
sriram veeraghantaandGitHub 14fe545709 Merge pull request #2156 from makeplane/stage/self-hosted-fixes
fix: selfhosted fixes
2023-09-12 21:06:01 +05:30
sriram veeraghanta bc99ec0f1d fix: selfhosted fixes (#2154)
* fix: selfhosted fixes

* fix: updated env example
2023-09-12 21:03:42 +05:30
sriram veeraghantaandGitHub cc63f67654 Merge pull request #2143 from makeplane/stage-release
promote: stage-release to master
2023-09-11 18:21:51 +05:30
sriram veeraghantaandGitHub b8dd9ca729 Merge pull request #2144 from makeplane/develop
promote: develop to stage release
2023-09-11 18:11:30 +05:30
Henit ChobisaandGitHub 6867154963 chore: added pre-release tag for workflow publications (#2133)
* chore: added pre-release tag for workflow publications

* chore: added backend services under a single image

* chore: exposed backend port for compose hub

* chore: removed backend exposed ports
2023-09-11 18:02:56 +05:30
NikhilandGitHub 73b360c2fd Merge pull request #2140 from makeplane/develop
promote: develop to stage-release
2023-09-11 14:39:11 +05:30
Aaryan KhandelwalandGitHub 7bb73b74ba refactor: priority icon component (#2132) 2023-09-11 14:35:58 +05:30
Dakshesh JainandGitHub 991258084e fix: query checking (#2137)
fix: the logic should be to check if object exist not if it's true or false
2023-09-11 13:21:50 +05:30
ThomasandGitHub 1a37668f0b fix: husky was removed in commit #2086, but prepare still uses it (#2128) 2023-09-11 13:05:09 +05:30
4447a4b519 fix: Tiptap comment card fix for space (#2129)
* fix:(space) fixed comment card's editor integration

* regression: removed content being set twice

* chore: added controller to manage tiptap editor

* chore: remove unused functions

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-09-11 12:54:19 +05:30
Dakshesh JainandGitHub 7842c4b2ea fix: authorize editor (#2122) 2023-09-11 12:24:46 +05:30
Aaryan KhandelwalandGitHub 8de93d0081 chore: remove getServerSideProps (#2130) 2023-09-11 12:13:00 +05:30
Aaryan KhandelwalandGitHub 5b228bd1eb chore: update state icons and colors (#2126)
* chore: update state icons and colors

* chore: update icons
2023-09-11 11:45:28 +05:30
Dakshesh JainandGitHub ad8a011bb9 fix: issue activity (#2127) 2023-09-11 11:44:16 +05:30
Aaryan KhandelwalandGitHub 49d0b3f4a1 fix: handleClose function of the export modal (#2124) 2023-09-08 13:29:06 +05:30
Aaryan KhandelwalandGitHub 1872dff00d fix: custom date filter not working on my issues and profile issues (#2123) 2023-09-08 13:28:32 +05:30
Dakshesh JainandGitHub faa6a2bcbc feat: select blocker, blocking, and parent (#2121)
* feat: update, delete link

refactor: using old fetch-key

* feat: issue activity with ability to view & add comment

feat: click on view more to view more options in the issue detail

* fix: upload image not working on mobile

* feat: select blocker, blocking, and parent

dev: auth layout for web-view, console.log callback for web-view

* style: made design consistant

* fix: displaying page only on web-view

* style: removed overflow hidden
2023-09-07 18:42:24 +05:30
sriram veeraghantaandGitHub 6d52707ff5 editor fixes for space (#2119) 2023-09-07 15:09:16 +05:30
NikhilandGitHub 8ba482bc9c chore: response status for project views update (#2111)
* chore: response status for project views update

* dev: remove 200 OK response from empty contents
2023-09-07 14:49:45 +05:30
sriram veeraghantaandGitHub 80bcca71ff Merge pull request #2091 from makeplane/stage-release
promote: stage release to master
2023-09-04 19:14:29 +05:30
sriram veeraghantaandGitHub 3db0ec819a Merge pull request #2090 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 18:53:41 +05:30
sriram veeraghantaandGitHub 414ea7371d Merge pull request #2085 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 18:16:47 +05:30
sriram veeraghantaandGitHub 2b84b7c18d Merge pull request #2076 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 14:48:53 +05:30
sriram veeraghantaandGitHub 674347c99e Merge pull request #2060 from makeplane/develop
Promote: Develop to Stage Release
2023-09-01 20:56:31 +05:30
sriram veeraghantaandGitHub c1102180e6 Merge pull request #2059 from makeplane/develop
Promote: Develop to Stage Release
2023-09-01 20:53:35 +05:30
sriram veeraghantaandGitHub 650c0c3b78 promote: develop to stage-release (#2045)
promote: develop to stage-release
2023-09-01 17:23:26 +05:30
sriram veeraghantaandGitHub 6f397710ce Merge pull request #1951 from makeplane/stage-release
Promote: Stage release to Production
2023-08-23 16:18:08 +05:30
sriram veeraghantaandGitHub e6bd6b6a8c Merge pull request #1950 from makeplane/develop
Promote: Develop to Stage Release
2023-08-23 15:49:14 +05:30
sriram veeraghantaandGitHub 9d3952006b Merge pull request #1948 from makeplane/stage-release
Promot: Stage Release to Production
2023-08-23 14:51:27 +05:30
sriram veeraghantaandGitHub b75473a684 Merge pull request #1947 from makeplane/develop
Promote: Develop to Stage Release
2023-08-23 12:37:25 +05:30
NikhilandGitHub 2f5bd58c61 Merge pull request #1941 from makeplane/stage-release
promote: stage-release to master
2023-08-22 19:37:49 +05:30
NikhilandGitHub e833fccf61 Merge pull request #1940 from makeplane/develop
dev: deploy docker containers (#1939)
2023-08-22 19:34:13 +05:30
NikhilandGitHub 62ba9abdb4 Merge pull request #1936 from makeplane/stage-release
promote: stage-release to master
2023-08-22 13:57:57 +05:30
NikhilandGitHub 46b138eb0b Merge pull request #1934 from makeplane/develop
fix: aws region changed for exporter (#1933)
2023-08-22 13:51:20 +05:30
NikhilandGitHub 59bdf222f5 Merge pull request #1932 from makeplane/stage-release
promote: stage release to master
2023-08-22 01:34:37 +05:30
NikhilandGitHub eb50ade5e3 Merge pull request #1931 from makeplane/develop
fix: access environment variables is changed in services (#1930)
2023-08-22 01:14:56 +05:30
Vamsi KuramaandGitHub 85a08e4abd Merge pull request #1929 from makeplane/stage-release 2023-08-22 00:17:29 +05:30
NikhilandGitHub aa2e1697b0 Merge pull request #1928 from makeplane/develop
promote: develop to stage-release
2023-08-21 20:51:46 +05:30
NikhilandGitHub 3beab9de6f Merge pull request #1923 from makeplane/develop
promote: develop to stage-release
2023-08-21 18:18:14 +05:30
NikhilandGitHub 13d21e752d Merge pull request #1910 from makeplane/develop
promote: develop to stage-release
2023-08-18 19:29:11 +05:30
Vamsi KuramaandGitHub 9ff8994c0e Merge pull request #1784 from makeplane/stage-release
promote: stage-release to master v0.10.1-patch
2023-08-03 18:55:24 +05:30
Vamsi KuramaandGitHub 3488001197 Merge pull request #1762 from makeplane/stage-release
promote: stage-release to master
2023-08-01 22:38:20 +05:30
Vamsi KuramaandGitHub 2ced7e4911 Merge pull request #1758 from makeplane/stage-release
promote: stage-release to master v0.10
2023-08-01 20:04:40 +05:30
Aaryan KhandelwalandGitHub e5a3bec28c Merge pull request #1709 from makeplane/stage-release
promote: stage-release to master
2023-07-28 19:47:04 +05:30
Vamsi KuramaandGitHub e930f8cc7b Merge pull request #1697 from makeplane/stage-release
promote: stage-release to master
2023-07-28 00:55:32 +05:30
112 changed files with 2251 additions and 907 deletions
+2
View File
@@ -23,6 +23,8 @@ NEXT_PUBLIC_SLACK_CLIENT_ID=""
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=""
# public boards deploy url
NEXT_PUBLIC_DEPLOY_URL=""
# plane deploy using nginx
NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
# Backend
# Debug value for api server use it as 0 for production use
+1 -1
View File
@@ -2,7 +2,7 @@ name: Update Docker Images for Plane on Release
on:
release:
types: [released]
types: [released, prereleased]
jobs:
build_push_backend:
+2 -3
View File
@@ -482,7 +482,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
# Delete joined project invites
project_invitations.delete()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_204_NO_CONTENT)
except Exception as e:
capture_exception(e)
return Response(
@@ -924,8 +924,7 @@ class ProjectUserViewsEndpoint(BaseAPIView):
project_member.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_204_NO_CONTENT)
except Project.DoesNotExist:
return Response(
{"error": "The requested resource does not exists"},
+2 -2
View File
@@ -532,7 +532,7 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet):
# Delete joined workspace invites
workspace_invitations.delete()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_204_NO_CONTENT)
except Exception as e:
capture_exception(e)
return Response(
@@ -846,7 +846,7 @@ class WorkspaceMemberUserViewsEndpoint(BaseAPIView):
workspace_member.view_props = request.data.get("view_props", {})
workspace_member.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_204_NO_CONTENT)
except WorkspaceMember.DoesNotExist:
return Response(
{"error": "User not a member of workspace"},
+2 -2
View File
@@ -85,7 +85,7 @@ services:
plane-worker:
container_name: planebgworker
image: makeplane/plane-worker:latest
image: makeplane/plane-backend:latest
restart: always
command: ./bin/worker
env_file:
@@ -99,7 +99,7 @@ services:
plane-beat-worker:
container_name: planebeatworker
image: makeplane/plane-worker:latest
image: makeplane/plane-backend:latest
restart: always
command: ./bin/beat
env_file:
-2
View File
@@ -90,8 +90,6 @@ services:
DOCKER_BUILDKIT: 1
restart: always
command: ./bin/takeoff
ports:
- 8000:8000
env_file:
- .env
environment:
-1
View File
@@ -8,7 +8,6 @@
"packages/*"
],
"scripts": {
"prepare": "husky install",
"build": "turbo run build",
"dev": "turbo run dev",
"start": "turbo run start",
+1 -1
View File
@@ -12,4 +12,4 @@ fi
# Only perform action if $FROM and $TO are different.
echo "Replacing all statically built instances of $FROM with this string $TO ."
grep -R -la "${FROM}" apps/$DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
grep -R -la "${FROM}" $DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"
+2
View File
@@ -0,0 +1,2 @@
// additional.d.ts
/// <reference types="next-images" />
+2 -2
View File
@@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
// components
import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts";
// images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
export const SignInView = observer(() => {
const { user: userStore } = useMobxStore();
@@ -112,7 +112,7 @@ export const SignInView = observer(() => {
<div className="fixed grid place-items-center bg-custom-background-100 sm:py-5 top-11 sm:top-12 left-7 sm:left-16 lg:left-28">
<div className="grid place-items-center bg-custom-background-100">
<div className="h-[30px] w-[30px]">
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" />
<img src={`${imagePrefix}/plane-logos/blue-without-text.png`} alt="Plane Logo" />
</div>
</div>
</div>
@@ -3,7 +3,7 @@ import React, { useState } from "react";
// mobx
import { observer } from "mobx-react-lite";
// react-hook-form
import { useForm, Controller } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Menu, Transition } from "@headlessui/react";
// lib
@@ -30,10 +30,13 @@ export const CommentCard: React.FC<Props> = observer((props) => {
// states
const [isEditing, setIsEditing] = useState(false);
const editorRef = React.useRef<any>(null);
const showEditorRef = React.useRef<any>(null);
const {
control,
formState: { isSubmitting },
handleSubmit,
control,
} = useForm<any>({
defaultValues: { comment_html: comment.comment_html },
});
@@ -47,6 +50,9 @@ export const CommentCard: React.FC<Props> = observer((props) => {
if (!workspaceSlug || !issueDetailStore.peekId) return;
issueDetailStore.updateIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id, formData);
setIsEditing(false);
editorRef.current?.setEditorValue(formData.comment_html);
showEditorRef.current?.setEditorValue(formData.comment_html);
};
return (
@@ -96,6 +102,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
render={({ field: { onChange, value } }) => (
<TipTapEditor
workspaceSlug={workspaceSlug as string}
ref={editorRef}
value={value}
debouncedUpdatesEnabled={false}
customClassName="min-h-[50px] p-3 shadow-sm"
@@ -125,7 +132,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
</form>
<div className={`${isEditing ? "hidden" : ""}`}>
<TipTapEditor
workspaceSlug={workspaceSlug.toString()}
workspaceSlug={workspaceSlug as string}
ref={showEditorRef}
value={comment.comment_html}
editable={false}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
@@ -44,7 +44,6 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mod
{mode === "full" && (
<div className="flex justify-between gap-2 pb-3">
<h6 className="flex items-center gap-2 font-medium">
{/* {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} */}
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
</h6>
<div className="flex items-center gap-2">
+10 -8
View File
@@ -77,14 +77,16 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
{...bubbleMenuProps}
className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl"
>
<NodeSelector
editor={props.editor!}
isOpen={isNodeSelectorOpen}
setIsOpen={() => {
setIsNodeSelectorOpen(!isNodeSelectorOpen);
setIsLinkSelectorOpen(false);
}}
/>
{!props.editor.isActive("table") && (
<NodeSelector
editor={props.editor!}
isOpen={isNodeSelectorOpen}
setIsOpen={() => {
setIsNodeSelectorOpen(!isNodeSelectorOpen);
setIsLinkSelectorOpen(false);
}}
/>
)}
<LinkSelector
editor={props.editor!!}
isOpen={isLinkSelectorOpen}
@@ -28,7 +28,10 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
name: "Text",
icon: TextIcon,
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
isActive: () =>
editor.isActive("paragraph") &&
!editor.isActive("bulletList") &&
!editor.isActive("orderedList"),
},
{
name: "H1",
@@ -69,7 +72,8 @@ export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen
{
name: "Quote",
icon: TextQuote,
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
command: () =>
editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
isActive: () => editor.isActive("blockquote"),
},
{
+117 -103
View File
@@ -13,6 +13,7 @@ import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
import { lowlight } from "lowlight/lib/core";
import SlashCommand from "../slash-command";
import { InputRule } from "@tiptap/core";
import Gapcursor from "@tiptap/extension-gapcursor";
import ts from "highlight.js/lib/languages/typescript";
@@ -20,6 +21,10 @@ import "highlight.js/styles/github-dark.css";
import UniqueID from "@tiptap-pro/extension-unique-id";
import UpdatedImage from "./updated-image";
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
import { CustomTableCell } from "./table/table-cell";
import { Table } from "./table/table";
import { TableHeader } from "./table/table-header";
import { TableRow } from "@tiptap/extension-table-row";
lowlight.registerLanguage("ts", ts);
@@ -27,113 +32,122 @@ export const TiptapExtensions = (
workspaceSlug: string,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) => [
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc list-outside leading-3 -mt-2",
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc list-outside leading-3 -mt-2",
},
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal list-outside leading-3 -mt-2",
orderedList: {
HTMLAttributes: {
class: "list-decimal list-outside leading-3 -mt-2",
},
},
},
listItem: {
HTMLAttributes: {
class: "leading-normal -mb-2",
listItem: {
HTMLAttributes: {
class: "leading-normal -mb-2",
},
},
},
blockquote: {
HTMLAttributes: {
class: "border-l-4 border-custom-border-300",
blockquote: {
HTMLAttributes: {
class: "border-l-4 border-custom-border-300",
},
},
},
code: {
HTMLAttributes: {
class: "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
spellcheck: "false",
code: {
HTMLAttributes: {
class:
"rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
spellcheck: "false",
},
},
},
codeBlock: false,
horizontalRule: false,
dropcursor: {
color: "#DBEAFE",
width: 2,
},
gapcursor: false,
}),
CodeBlockLowlight.configure({
lowlight,
}),
HorizontalRule.extend({
addInputRules() {
return [
new InputRule({
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
handler: ({ state, range, commands }) => {
commands.splitBlock();
codeBlock: false,
horizontalRule: false,
dropcursor: {
color: "rgba(var(--color-text-100))",
width: 2,
},
gapcursor: false,
}),
CodeBlockLowlight.configure({
lowlight,
}),
HorizontalRule.extend({
addInputRules() {
return [
new InputRule({
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
handler: ({ state, range, commands }) => {
commands.splitBlock();
const attributes = {};
const { tr } = state;
const start = range.from;
const end = range.to;
// @ts-ignore
tr.replaceWith(start - 1, end, this.type.create(attributes));
},
}),
];
},
}).configure({
HTMLAttributes: {
class: "mb-6 border-t border-custom-border-300",
},
}),
TiptapLink.configure({
protocols: ["http", "https"],
validate: (url) => isValidHttpUrl(url),
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
UpdatedImage.configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === "heading") {
return `Heading ${node.attrs.level}`;
}
const attributes = {};
const { tr } = state;
const start = range.from;
const end = range.to;
// @ts-ignore
tr.replaceWith(start - 1, end, this.type.create(attributes));
},
}),
];
},
}).configure({
HTMLAttributes: {
class: "mb-6 border-t border-custom-border-300",
},
}),
Gapcursor,
TiptapLink.configure({
protocols: ["http", "https"],
validate: (url) => isValidHttpUrl(url),
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
UpdatedImage.configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === "heading") {
return `Heading ${node.attrs.level}`;
}
if (node.type.name === "image" || node.type.name === "table") {
return "";
}
return "Press '/' for commands...";
},
includeChildren: true,
}),
UniqueID.configure({
types: ["image"],
}),
SlashCommand(workspaceSlug, setIsSubmitting),
TiptapUnderline,
TextStyle,
Color,
Highlight.configure({
multicolor: true,
}),
TaskList.configure({
HTMLAttributes: {
class: "not-prose pl-2",
},
}),
TaskItem.configure({
HTMLAttributes: {
class: "flex items-start my-4",
},
nested: true,
}),
Markdown.configure({
html: true,
transformCopiedText: true,
}),
];
return "Press '/' for commands...";
},
includeChildren: true,
}),
UniqueID.configure({
types: ["image"],
}),
SlashCommand(workspaceSlug, setIsSubmitting),
TiptapUnderline,
TextStyle,
Color,
Highlight.configure({
multicolor: true,
}),
TaskList.configure({
HTMLAttributes: {
class: "not-prose pl-2",
},
}),
TaskItem.configure({
HTMLAttributes: {
class: "flex items-start my-4",
},
nested: true,
}),
Markdown.configure({
html: true,
transformCopiedText: true,
}),
Table,
TableHeader,
CustomTableCell,
TableRow,
];
@@ -0,0 +1,32 @@
import { TableCell } from "@tiptap/extension-table-cell";
export const CustomTableCell = TableCell.extend({
addAttributes() {
return {
...this.parent?.(),
isHeader: {
default: false,
parseHTML: (element) => {
isHeader: element.tagName === "TD";
},
renderHTML: (attributes) => {
tag: attributes.isHeader ? "th" : "td";
},
},
};
},
renderHTML({ HTMLAttributes }) {
if (HTMLAttributes.isHeader) {
return [
"th",
{
...HTMLAttributes,
class: `relative ${HTMLAttributes.class}`,
},
["span", { class: "absolute top-0 right-0" }],
0,
];
}
return ["td", HTMLAttributes, 0];
},
});
@@ -0,0 +1,7 @@
import { TableHeader as BaseTableHeader } from "@tiptap/extension-table-header";
const TableHeader = BaseTableHeader.extend({
content: "paragraph",
});
export { TableHeader };
@@ -0,0 +1,9 @@
import { Table as BaseTable } from "@tiptap/extension-table";
const Table = BaseTable.configure({
resizable: true,
cellMinWidth: 100,
allowTableNodeSelection: true,
});
export { Table };
+5 -8
View File
@@ -6,6 +6,7 @@ import { EditorBubbleMenu } from "./bubble-menu";
import { TiptapExtensions } from "./extensions";
import { TiptapEditorProps } from "./props";
import { ImageResizer } from "./extensions/image-resize";
import { TableMenu } from "./table-menu";
export interface ITipTapRichTextEditor {
value: string;
@@ -37,6 +38,7 @@ const Tiptap = (props: ITipTapRichTextEditor) => {
borderOnFocus,
customClassName,
} = props;
const editor = useEditor({
editable: editable ?? true,
editorProps: TiptapEditorProps(workspaceSlug, setIsSubmitting),
@@ -54,12 +56,6 @@ const Tiptap = (props: ITipTapRichTextEditor) => {
},
});
useEffect(() => {
if (editor) {
editor.commands.setContent(value);
}
}, [value]);
const editorRef: React.MutableRefObject<Editor | null> = useRef(null);
useImperativeHandle(forwardedRef, () => ({
@@ -81,8 +77,8 @@ const Tiptap = (props: ITipTapRichTextEditor) => {
const editorClassNames = `relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md
${noBorder ? "" : "border border-custom-border-200"} ${
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
} ${customClassName}`;
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
} ${customClassName}`;
if (!editor) return null;
editorRef.current = editor;
@@ -98,6 +94,7 @@ const Tiptap = (props: ITipTapRichTextEditor) => {
{editor && <EditorBubbleMenu editor={editor} />}
<div className={`${editorContentCustomClassNames}`}>
<EditorContent editor={editor} />
<TableMenu editor={editor} />
{editor?.isActive("image") && <ImageResizer editor={editor} />}
</div>
</div>
@@ -1,43 +1,51 @@
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import fileService from "services/file.service";
const deleteKey = new PluginKey("delete-image");
const IMAGE_NODE_TYPE = "image";
const TrackImageDeletionPlugin = () =>
interface ImageNode extends ProseMirrorNode {
attrs: {
src: string;
id: string;
};
}
const TrackImageDeletionPlugin = (): Plugin =>
new Plugin({
key: deleteKey,
appendTransaction: (transactions, oldState, newState) => {
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
const newImageSources = new Set();
newState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
newImageSources.add(node.attrs.src);
}
});
transactions.forEach((transaction) => {
if (!transaction.docChanged) return;
const removedImages: ProseMirrorNode[] = [];
const removedImages: ImageNode[] = [];
oldState.doc.descendants((oldNode, oldPos) => {
if (oldNode.type.name !== "image") return;
if (oldNode.type.name !== IMAGE_NODE_TYPE) return;
if (oldPos < 0 || oldPos > newState.doc.content.size) return;
if (!newState.doc.resolve(oldPos).parent) return;
const newNode = newState.doc.nodeAt(oldPos);
// Check if the node has been deleted or replaced
if (!newNode || newNode.type.name !== "image") {
// Check if the node still exists elsewhere in the document
let nodeExists = false;
newState.doc.descendants((node) => {
if (node.attrs.id === oldNode.attrs.id) {
nodeExists = true;
}
});
if (!nodeExists) {
removedImages.push(oldNode as ProseMirrorNode);
if (!newNode || newNode.type.name !== IMAGE_NODE_TYPE) {
if (!newImageSources.has(oldNode.attrs.src)) {
removedImages.push(oldNode as ImageNode);
}
}
});
removedImages.forEach((node) => {
removedImages.forEach(async (node) => {
const src = node.attrs.src;
onNodeDeleted(src);
await onNodeDeleted(src);
});
});
@@ -47,10 +55,14 @@ const TrackImageDeletionPlugin = () =>
export default TrackImageDeletionPlugin;
async function onNodeDeleted(src: string) {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId);
if (resStatus === 204) {
console.log("Image deleted successfully");
async function onNodeDeleted(src: string): Promise<void> {
try {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId);
if (resStatus === 204) {
console.log("Image deleted successfully");
}
} catch (error) {
console.error("Error deleting image: ", error);
}
}
@@ -1,4 +1,3 @@
// @ts-nocheck
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
import fileService from "services/file.service";
@@ -46,7 +45,11 @@ export default UploadImagesPlugin;
function findPlaceholder(state: EditorState, id: {}) {
const decos = uploadKey.getState(state);
const found = decos.find(undefined, undefined, (spec: { id: number | undefined }) => spec.id == id);
const found = decos.find(
undefined,
undefined,
(spec: { id: number | undefined }) => spec.id == id
);
return found.length ? found[0].from : null;
}
@@ -59,8 +62,6 @@ export async function startImageUpload(
) {
if (!file.type.includes("image/")) {
return;
} else if (file.size / 1024 / 1024 > 20) {
return;
}
const id = {};
@@ -93,7 +94,9 @@ export async function startImageUpload(
const imageSrc = typeof src === "object" ? reader.result : src;
const node = schema.nodes.image.create({ src: imageSrc });
const transaction = view.state.tr.replaceWith(pos, pos, node).setMeta(uploadKey, { remove: { id } });
const transaction = view.state.tr
.replaceWith(pos, pos, node)
.setMeta(uploadKey, { remove: { id } });
view.dispatch(transaction);
}
@@ -107,7 +110,9 @@ const UploadImageHandler = (file: File, workspaceSlug: string): Promise<string>
formData.append("attributes", JSON.stringify({}));
return new Promise(async (resolve, reject) => {
const imageUrl = await fileService.uploadFile(workspaceSlug, formData).then((response) => response.asset);
const imageUrl = await fileService
.uploadFile(workspaceSlug, formData)
.then((response) => response.asset);
const image = new Image();
image.src = imageUrl;
+19
View File
@@ -1,5 +1,6 @@
import { EditorProps } from "@tiptap/pm/view";
import { startImageUpload } from "./plugins/upload-image";
import { findTableAncestor } from "./table-menu";
export function TiptapEditorProps(
workspaceSlug: string,
@@ -21,6 +22,15 @@ export function TiptapEditorProps(
},
},
handlePaste: (view, event) => {
if (typeof window !== "undefined") {
const selection: any = window?.getSelection();
if (selection.rangeCount !== 0) {
const range = selection.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return;
}
}
}
if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
event.preventDefault();
const file = event.clipboardData.files[0];
@@ -31,6 +41,15 @@ export function TiptapEditorProps(
return false;
},
handleDrop: (view, event, _slice, moved) => {
if (typeof window !== "undefined") {
const selection: any = window?.getSelection();
if (selection.rangeCount !== 0) {
const range = selection.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return;
}
}
}
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
event.preventDefault();
const file = event.dataTransfer.files[0];
@@ -15,6 +15,7 @@ import {
MinusSquare,
CheckSquare,
ImageIcon,
Table,
} from "lucide-react";
import { startImageUpload } from "../plugins/upload-image";
import { cn } from "../utils";
@@ -46,6 +47,9 @@ const Command = Extension.create({
return [
Suggestion({
editor: this.editor,
allow({ editor }) {
return !editor.isActive("table");
},
...this.options.suggestion,
}),
];
@@ -53,7 +57,10 @@ const Command = Extension.create({
});
const getSuggestionItems =
(workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void) =>
(
workspaceSlug: string,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) =>
({ query }: { query: string }) =>
[
{
@@ -119,6 +126,20 @@ const getSuggestionItems =
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
},
},
{
title: "Table",
description: "Create a Table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table size={18} />,
command: ({ editor, range }: CommandProps) => {
editor
.chain()
.focus()
.deleteRange(range)
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run();
},
},
{
title: "Numbered List",
description: "Create a list with numbering.",
@@ -134,14 +155,21 @@ const getSuggestionItems =
searchTerms: ["blockquote"],
icon: <TextQuote size={18} />,
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
editor
.chain()
.focus()
.deleteRange(range)
.toggleNode("paragraph", "paragraph")
.toggleBlockquote()
.run(),
},
{
title: "Code",
description: "Capture a code snippet.",
searchTerms: ["codeblock"],
icon: <Code size={18} />,
command: ({ editor, range }: CommandProps) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
title: "Image",
@@ -190,7 +218,15 @@ export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
}
};
const CommandList = ({ items, command }: { items: CommandItemProps[]; command: any; editor: any; range: any }) => {
const CommandList = ({
items,
command,
}: {
items: CommandItemProps[];
command: any;
editor: any;
range: any;
}) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = useCallback(
@@ -0,0 +1,16 @@
const InsertBottomTableIcon = (props: any) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 -960 960 960"
{...props}
>
<path
d="M212.309-152.31q-30.308 0-51.308-21t-21-51.307V-360q0-30.307 21-51.307 21-21 51.308-21h535.382q30.308 0 51.308 21t21 51.307v135.383q0 30.307-21 51.307-21 21-51.308 21H212.309Zm0-375.383q-30.308 0-51.308-21t-21-51.307v-135.383q0-30.307 21-51.307 21-21 51.308-21h535.382q30.308 0 51.308 21t21 51.307V-600q0 30.307-21 51.307-21 21-51.308 21H212.309Zm535.382-219.998H212.309q-4.616 0-8.463 3.846-3.846 3.846-3.846 8.462V-600q0 4.616 3.846 8.462 3.847 3.847 8.463 3.847h535.382q4.616 0 8.463-3.847Q760-595.384 760-600v-135.383q0-4.616-3.846-8.462-3.847-3.846-8.463-3.846ZM200-587.691v-160 160Z"
fill="rgb(var(--color-text-300))"
/>
</svg>
);
export default InsertBottomTableIcon;
@@ -0,0 +1,15 @@
const InsertLeftTableIcon = (props: any) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 -960 960 960"
{...props}
>
<path
d="M224.617-140.001q-30.307 0-51.307-21-21-21-21-51.308v-535.382q0-30.308 21-51.308t51.307-21H360q30.307 0 51.307 21 21 21 21 51.308v535.382q0 30.308-21 51.308t-51.307 21H224.617Zm375.383 0q-30.307 0-51.307-21-21-21-21-51.308v-535.382q0-30.308 21-51.308t51.307-21h135.383q30.307 0 51.307 21 21 21 21 51.308v535.382q0 30.308-21 51.308t-51.307 21H600Zm147.691-607.69q0-4.616-3.846-8.463-3.846-3.846-8.462-3.846H600q-4.616 0-8.462 3.846-3.847 3.847-3.847 8.463v535.382q0 4.616 3.847 8.463Q595.384-200 600-200h135.383q4.616 0 8.462-3.846 3.846-3.847 3.846-8.463v-535.382ZM587.691-200h160-160Z"
fill="rgb(var(--color-text-300))"
/>
</svg>
);
export default InsertLeftTableIcon;
@@ -0,0 +1,16 @@
const InsertRightTableIcon = (props: any) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 -960 960 960"
{...props}
>
<path
d="M600-140.001q-30.307 0-51.307-21-21-21-21-51.308v-535.382q0-30.308 21-51.308t51.307-21h135.383q30.307 0 51.307 21 21 21 21 51.308v535.382q0 30.308-21 51.308t-51.307 21H600Zm-375.383 0q-30.307 0-51.307-21-21-21-21-51.308v-535.382q0-30.308 21-51.308t51.307-21H360q30.307 0 51.307 21 21 21 21 51.308v535.382q0 30.308-21 51.308t-51.307 21H224.617Zm-12.308-607.69v535.382q0 4.616 3.846 8.463 3.846 3.846 8.462 3.846H360q4.616 0 8.462-3.846 3.847-3.847 3.847-8.463v-535.382q0-4.616-3.847-8.463Q364.616-760 360-760H224.617q-4.616 0-8.462 3.846-3.846 3.847-3.846 8.463Zm160 547.691h-160 160Z"
fill="rgb(var(--color-text-300))"
/>
</svg>
);
export default InsertRightTableIcon;
@@ -0,0 +1,15 @@
const InsertTopTableIcon = (props: any) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 -960 960 960"
{...props}
>
<path
d="M212.309-527.693q-30.308 0-51.308-21t-21-51.307v-135.383q0-30.307 21-51.307 21-21 51.308-21h535.382q30.308 0 51.308 21t21 51.307V-600q0 30.307-21 51.307-21 21-51.308 21H212.309Zm0 375.383q-30.308 0-51.308-21t-21-51.307V-360q0-30.307 21-51.307 21-21 51.308-21h535.382q30.308 0 51.308 21t21 51.307v135.383q0 30.307-21 51.307-21 21-51.308 21H212.309Zm0-59.999h535.382q4.616 0 8.463-3.846 3.846-3.846 3.846-8.462V-360q0-4.616-3.846-8.462-3.847-3.847-8.463-3.847H212.309q-4.616 0-8.463 3.847Q200-364.616 200-360v135.383q0 4.616 3.846 8.462 3.847 3.846 8.463 3.846Zm-12.309-160v160-160Z"
fill="rgb(var(--color-text-300))"
/>
</svg>
);
export default InsertTopTableIcon;
@@ -0,0 +1,143 @@
import { useState, useEffect } from "react";
import { Rows, Columns, ToggleRight } from "lucide-react";
import { cn } from "../utils";
import { Tooltip } from "components/ui";
import InsertLeftTableIcon from "./InsertLeftTableIcon";
import InsertRightTableIcon from "./InsertRightTableIcon";
import InsertTopTableIcon from "./InsertTopTableIcon";
import InsertBottomTableIcon from "./InsertBottomTableIcon";
interface TableMenuItem {
command: () => void;
icon: any;
key: string;
name: string;
}
export const findTableAncestor = (node: Node | null): HTMLTableElement | null => {
while (node !== null && node.nodeName !== "TABLE") {
node = node.parentNode;
}
return node as HTMLTableElement;
};
export const TableMenu = ({ editor }: { editor: any }) => {
const [tableLocation, setTableLocation] = useState({ bottom: 0, left: 0 });
const isOpen = editor?.isActive("table");
const items: TableMenuItem[] = [
{
command: () => editor.chain().focus().addColumnBefore().run(),
icon: InsertLeftTableIcon,
key: "insert-column-left",
name: "Insert 1 column left",
},
{
command: () => editor.chain().focus().addColumnAfter().run(),
icon: InsertRightTableIcon,
key: "insert-column-right",
name: "Insert 1 column right",
},
{
command: () => editor.chain().focus().addRowBefore().run(),
icon: InsertTopTableIcon,
key: "insert-row-above",
name: "Insert 1 row above",
},
{
command: () => editor.chain().focus().addRowAfter().run(),
icon: InsertBottomTableIcon,
key: "insert-row-below",
name: "Insert 1 row below",
},
{
command: () => editor.chain().focus().deleteColumn().run(),
icon: Columns,
key: "delete-column",
name: "Delete column",
},
{
command: () => editor.chain().focus().deleteRow().run(),
icon: Rows,
key: "delete-row",
name: "Delete row",
},
{
command: () => editor.chain().focus().toggleHeaderRow().run(),
icon: ToggleRight,
key: "toggle-header-row",
name: "Toggle header row",
},
];
useEffect(() => {
if (!window) return;
const handleWindowClick = () => {
const selection: any = window?.getSelection();
if (selection.rangeCount !== 0) {
const range = selection.getRangeAt(0);
const tableNode = findTableAncestor(range.startContainer);
let parent = tableNode?.parentElement;
if (tableNode) {
const tableRect = tableNode.getBoundingClientRect();
const tableCenter = tableRect.left + tableRect.width / 2;
const menuWidth = 45;
const menuLeft = tableCenter - menuWidth / 2;
const tableBottom = tableRect.bottom;
setTableLocation({ bottom: tableBottom, left: menuLeft });
while (parent) {
if (!parent.classList.contains("disable-scroll"))
parent.classList.add("disable-scroll");
parent = parent.parentElement;
}
} else {
const scrollDisabledContainers = document.querySelectorAll(".disable-scroll");
scrollDisabledContainers.forEach((container) => {
container.classList.remove("disable-scroll");
});
}
}
};
window.addEventListener("click", handleWindowClick);
return () => {
window.removeEventListener("click", handleWindowClick);
};
}, [tableLocation, editor]);
return (
<section
className={`fixed left-1/2 transform -translate-x-1/2 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 shadow-custom-shadow-sm p-1 ${
isOpen ? "block" : "hidden"
}`}
style={{
bottom: `calc(100vh - ${tableLocation.bottom + 45}px)`,
left: `${tableLocation.left}px`,
}}
>
{items.map((item, index) => (
<Tooltip key={index} tooltipContent={item.name}>
<button
onClick={item.command}
className="p-1.5 text-custom-text-200 hover:bg-text-custom-text-100 hover:bg-custom-background-80 active:bg-custom-background-80 rounded"
title={item.name}
>
<item.icon
className={cn("h-4 w-4 text-lg", {
"text-red-600": item.key.includes("delete"),
})}
/>
</button>
</Tooltip>
))}
</section>
);
};
+4 -1
View File
@@ -12,7 +12,10 @@ const nextConfig = {
};
if (parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) {
const nextConfigWithNginx = withImages({ basePath: "/spaces", ...nextConfig });
const nextConfigWithNginx = withImages({
basePath: "/spaces",
...nextConfig,
});
module.exports = nextConfigWithNginx;
} else {
module.exports = nextConfig;
+1
View File
@@ -69,6 +69,7 @@
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"autoprefixer": "^10.4.13",
"eslint": "8.34.0",
"eslint-config-custom": "*",
@@ -1,7 +1,8 @@
import useSWR from "swr";
import type { GetServerSideProps } from "next";
import { useRouter } from "next/router";
import Head from "next/head";
import { useRouter } from "next/router";
import useSWR from "swr";
/// layouts
import ProjectLayout from "layouts/project-layout";
// components
@@ -39,12 +40,4 @@ const WorkspaceProjectPage = (props: any) => {
);
};
// export const getServerSideProps: GetServerSideProps<any> = async ({ query: { workspace_slug, project_slug } }) => {
// const res = await fetch(
// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/settings/`
// );
// const project_settings = await res.json();
// return { props: { project_settings } };
// };
export default WorkspaceProjectPage;
+3 -4
View File
@@ -1,7 +1,4 @@
import React, { useEffect } from "react";
import Image from "next/image";
// assets
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
// mobx
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
@@ -12,6 +9,8 @@ import useToast from "hooks/use-toast";
// components
import { OnBoardingForm } from "components/accounts/onboarding-form";
const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
const OnBoardingPage = () => {
const { user: userStore } = useMobxStore();
@@ -34,7 +33,7 @@ const OnBoardingPage = () => {
<div className="absolute border-b-[0.5px] sm:border-r-[0.5px] border-custom-border-200 h-[0.5px] w-full top-1/2 left-0 -translate-y-1/2 sm:h-screen sm:w-[0.5px] sm:top-0 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 sm:translate-y-0 z-10" />
<div className="absolute grid place-items-center bg-custom-background-100 px-3 sm:px-0 py-5 left-2 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 top-1/2 -translate-y-1/2 sm:translate-y-0 sm:top-12 z-10">
<div className="h-[30px] w-[30px]">
<Image src={BluePlaneLogoWithoutText} alt="Plane logo" />
<img src={`${imagePrefix}/plane-logos/blue-without-text.png`} alt="Plane logo" />
</div>
</div>
<div className="absolute sm:fixed text-custom-text-100 text-sm font-medium right-4 top-1/4 sm:top-12 -translate-y-1/2 sm:translate-y-0 sm:right-16 sm:py-5">
+1 -1
View File
@@ -1,6 +1,6 @@
{
"extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"baseUrl": ".",
@@ -1,13 +1,13 @@
// nivo
import { BarDatum } from "@nivo/bar";
// icons
import { getPriorityIcon } from "components/icons";
import { PriorityIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// helpers
import { generateBarColor, renderMonthAndYear } from "helpers/analytics.helper";
// types
import { IAnalyticsParams, IAnalyticsResponse } from "types";
import { IAnalyticsParams, IAnalyticsResponse, TIssuePriorities } from "types";
// constants
import { ANALYTICS_X_AXIS_VALUES, ANALYTICS_Y_AXIS_VALUES, DATE_KEYS } from "constants/analytics";
@@ -53,7 +53,7 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
>
<div className="flex items-center gap-2">
{params.segment === "priority" ? (
getPriorityIcon(key)
<PriorityIcon priority={key as TIssuePriorities} />
) : (
<span
className="h-3 w-3 flex-shrink-0 rounded"
@@ -91,7 +91,7 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
}`}
>
{params.x_axis === "priority" ? (
getPriorityIcon(`${item.name}`)
<PriorityIcon priority={item.name as TIssuePriorities} />
) : (
<span
className="h-3 w-3 rounded"
@@ -1,7 +1,7 @@
// icons
import { PlayIcon } from "@heroicons/react/24/outline";
// types
import { IDefaultAnalyticsResponse } from "types";
import { IDefaultAnalyticsResponse, TStateGroups } from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
@@ -27,7 +27,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<span
className="h-2 w-2 rounded-full"
style={{
backgroundColor: STATE_GROUP_COLORS[group.state_group],
backgroundColor: STATE_GROUP_COLORS[group.state_group as TStateGroups],
}}
/>
<h6 className="capitalize">{group.state_group}</h6>
@@ -42,7 +42,7 @@ export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
className="absolute top-0 left-0 h-1 rounded duration-300"
style={{
width: `${percentage}%`,
backgroundColor: STATE_GROUP_COLORS[group.state_group],
backgroundColor: STATE_GROUP_COLORS[group.state_group as TStateGroups],
}}
/>
</div>
@@ -9,7 +9,7 @@ import { CustomSearchSelect, CustomSelect, ToggleSwitch } from "components/ui";
import { SelectMonthModal } from "components/automation";
// icons
import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// services
import stateService from "services/state.service";
// constants
@@ -46,7 +46,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
query: state.name,
content: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)}
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
{state.name}
</div>
),
@@ -140,14 +140,19 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
label={
<div className="flex items-center gap-2">
{selectedOption ? (
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)
<StateGroupIcon
stateGroup={selectedOption.group}
color={selectedOption.color}
height="16px"
width="16px"
/>
) : currentDefaultState ? (
getStateGroupIcon(
currentDefaultState.group,
"16",
"16",
currentDefaultState.color
)
<StateGroupIcon
stateGroup={currentDefaultState.group}
color={currentDefaultState.color}
height="16px"
width="16px"
/>
) : (
<Squares2X2Icon className="h-3.5 w-3.5 text-custom-text-200" />
)}
@@ -1,5 +1,7 @@
import { useRouter } from "next/router";
import React, { Dispatch, SetStateAction, useCallback } from "react";
import { useRouter } from "next/router";
import { mutate } from "swr";
// cmdk
@@ -7,12 +9,12 @@ import { Command } from "cmdk";
// services
import issuesService from "services/issues.service";
// types
import { ICurrentUserResponse, IIssue } from "types";
import { ICurrentUserResponse, IIssue, TIssuePriorities } from "types";
// constants
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
import { PRIORITIES } from "constants/project";
// icons
import { CheckIcon, getPriorityIcon } from "components/icons";
import { CheckIcon, PriorityIcon } from "components/icons";
type Props = {
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
@@ -54,7 +56,7 @@ export const ChangeIssuePriority: React.FC<Props> = ({ setIsPaletteOpen, issue,
[workspaceSlug, issueId, projectId, user]
);
const handleIssueState = (priority: string | null) => {
const handleIssueState = (priority: TIssuePriorities) => {
submitChanges({ priority });
setIsPaletteOpen(false);
};
@@ -68,7 +70,7 @@ export const ChangeIssuePriority: React.FC<Props> = ({ setIsPaletteOpen, issue,
className="focus:outline-none"
>
<div className="flex items-center space-x-3">
{getPriorityIcon(priority)}
<PriorityIcon priority={priority} />
<span className="capitalize">{priority ?? "None"}</span>
</div>
<div>{priority === issue.priority && <CheckIcon className="h-3 w-3" />}</div>
@@ -1,22 +1,24 @@
import { useRouter } from "next/router";
import React, { Dispatch, SetStateAction, useCallback } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// cmdk
import { Command } from "cmdk";
// ui
import { Spinner } from "components/ui";
// helpers
import { getStatesList } from "helpers/state.helper";
// services
import issuesService from "services/issues.service";
import stateService from "services/state.service";
// ui
import { Spinner } from "components/ui";
// icons
import { CheckIcon, StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { ICurrentUserResponse, IIssue } from "types";
// fetch keys
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY, STATES_LIST } from "constants/fetch-keys";
// icons
import { CheckIcon, getStateGroupIcon } from "components/icons";
type Props = {
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
@@ -82,7 +84,12 @@ export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue, use
className="focus:outline-none"
>
<div className="flex items-center space-x-3">
{getStateGroupIcon(state.group, "16", "16", state.color)}
<StateGroupIcon
stateGroup={state.group}
color={state.color}
height="16px"
width="16px"
/>
<p>{state.name}</p>
</div>
<div>{state.id === issue.state && <CheckIcon className="h-3 w-3" />}</div>
@@ -1,15 +1,11 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// react-datepicker
import DatePicker from "react-datepicker";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// hooks
import useIssuesView from "hooks/use-issues-view";
// components
import { DateFilterSelect } from "./date-filter-select";
// ui
@@ -23,8 +19,10 @@ import { IIssueFilterOptions } from "types";
type Props = {
title: string;
field: keyof IIssueFilterOptions;
isOpen: boolean;
filters: IIssueFilterOptions;
handleClose: () => void;
isOpen: boolean;
onSelect: (option: any) => void;
};
type TFormValues = {
@@ -39,12 +37,14 @@ const defaultValues: TFormValues = {
date2: new Date(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()),
};
export const DateFilterModal: React.FC<Props> = ({ title, field, isOpen, handleClose }) => {
const { filters, setFilters } = useIssuesView();
const router = useRouter();
const { viewId } = router.query;
export const DateFilterModal: React.FC<Props> = ({
title,
field,
filters,
handleClose,
isOpen,
onSelect,
}) => {
const { handleSubmit, watch, control } = useForm<TFormValues>({
defaultValues,
});
@@ -53,10 +53,10 @@ export const DateFilterModal: React.FC<Props> = ({ title, field, isOpen, handleC
const { filterType, date1, date2 } = formData;
if (filterType === "range") {
setFilters(
{ [field]: [`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`] },
!Boolean(viewId)
);
onSelect({
key: field,
value: [`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`],
});
} else {
const filteredArray = (filters?.[field] as string[])?.filter((item) => {
if (item?.includes(filterType)) return false;
@@ -66,17 +66,12 @@ export const DateFilterModal: React.FC<Props> = ({ title, field, isOpen, handleC
const filterOne = filteredArray && filteredArray?.length > 0 ? filteredArray[0] : null;
if (filterOne)
setFilters(
{ [field]: [filterOne, `${renderDateFormat(date1)};${filterType}`] },
!Boolean(viewId)
);
onSelect({ key: field, value: [filterOne, `${renderDateFormat(date1)};${filterType}`] });
else
setFilters(
{
[field]: [`${renderDateFormat(date1)};${filterType}`],
},
!Boolean(viewId)
);
onSelect({
key: field,
value: [`${renderDateFormat(date1)};${filterType}`],
});
}
handleClose();
};
+11 -9
View File
@@ -2,7 +2,7 @@ import React from "react";
// icons
import { XMarkIcon } from "@heroicons/react/24/outline";
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
import { PriorityIcon, StateGroupIcon } from "components/icons";
// ui
import { Avatar } from "components/ui";
// helpers
@@ -71,12 +71,10 @@ export const FiltersList: React.FC<Props> = ({
}}
>
<span>
{getStateGroupIcon(
state?.group ?? "backlog",
"12",
"12",
state?.color
)}
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
/>
</span>
<span>{state?.name ?? ""}</span>
<span
@@ -105,7 +103,9 @@ export const FiltersList: React.FC<Props> = ({
backgroundColor: `${STATE_GROUP_COLORS[group]}20`,
}}
>
<span>{getStateGroupIcon(group, "16", "16")}</span>
<span>
<StateGroupIcon stateGroup={group} color={undefined} />
</span>
<span>{group}</span>
<span
className="cursor-pointer"
@@ -136,7 +136,9 @@ export const FiltersList: React.FC<Props> = ({
: "bg-custom-background-90 text-custom-text-200"
}`}
>
<span>{getPriorityIcon(priority)}</span>
<span>
<PriorityIcon priority={priority} />
</span>
<span>{priority === "null" ? "None" : priority}</span>
<span
className="cursor-pointer"
@@ -15,6 +15,7 @@ import {
TAssigneesDistribution,
TCompletionChartDistribution,
TLabelsDistribution,
TStateGroups,
} from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
@@ -215,7 +216,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
<span
className="block h-3 w-3 rounded-full "
style={{
backgroundColor: STATE_GROUP_COLORS[group],
backgroundColor: STATE_GROUP_COLORS[group as TStateGroups],
}}
/>
<span className="text-xs capitalize">{group}</span>
@@ -1,7 +1,7 @@
// components
import { SingleBoard } from "components/core/views/board-view/single-board";
// icons
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
// types
@@ -82,8 +82,14 @@ export const AllBoards: React.FC<Props> = ({
className="flex items-center justify-between gap-2 rounded bg-custom-background-90 p-2 shadow"
>
<div className="flex items-center gap-2">
{currentState &&
getStateGroupIcon(currentState.group, "16", "16", currentState.color)}
{currentState && (
<StateGroupIcon
stateGroup={currentState.group}
color={currentState.color}
height="16px"
width="16px"
/>
)}
<h4 className="text-sm capitalize">
{selectedGroup === "state"
? addSpaceIfCamelCase(currentState?.name ?? "")
@@ -13,14 +13,16 @@ import useProjects from "hooks/use-projects";
import { Avatar, Icon } from "components/ui";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
import { PriorityIcon, StateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
// types
import { IIssueViewProps, IState } from "types";
import { IIssueViewProps, IState, TIssuePriorities, TStateGroups } from "types";
// fetch-keys
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
type Props = {
currentState?: IState | null;
@@ -97,14 +99,27 @@ export const BoardHeader: React.FC<Props> = ({
switch (selectedGroup) {
case "state":
icon =
currentState && getStateGroupIcon(currentState.group, "16", "16", currentState.color);
icon = currentState && (
<StateGroupIcon
stateGroup={currentState.group}
color={currentState.color}
height="16px"
width="16px"
/>
);
break;
case "state_detail.group":
icon = getStateGroupIcon(groupTitle as any, "16", "16");
icon = (
<StateGroupIcon
stateGroup={groupTitle as TStateGroups}
color={STATE_GROUP_COLORS[groupTitle as TStateGroups]}
height="16px"
width="16px"
/>
);
break;
case "priority":
icon = getPriorityIcon(groupTitle, "text-lg");
icon = <PriorityIcon priority={groupTitle as TIssuePriorities} className="text-lg" />;
break;
case "project":
const project = projects?.find((p) => p.id === groupTitle);
+3 -2
View File
@@ -29,7 +29,7 @@ import { PlusIcon } from "@heroicons/react/24/outline";
import { getStatesList } from "helpers/state.helper";
import { orderArrayBy } from "helpers/array.helper";
// types
import { IIssue, IIssueFilterOptions, IState } from "types";
import { IIssue, IIssueFilterOptions, IState, TIssuePriorities } from "types";
// fetch-keys
import {
CYCLE_DETAILS,
@@ -184,7 +184,8 @@ export const IssuesView: React.FC<Props> = ({
// if the issue is moved to a different group, then we will change the group of the
// dragged item(or issue)
if (selectedGroup === "priority") draggedItem.priority = destinationGroup;
if (selectedGroup === "priority")
draggedItem.priority = destinationGroup as TIssuePriorities;
else if (selectedGroup === "state") {
draggedItem.state = destinationGroup;
draggedItem.state_detail = states?.find((s) => s.id === destinationGroup) as IState;
@@ -15,7 +15,7 @@ import { SingleListIssue } from "components/core";
import { Avatar, CustomMenu } from "components/ui";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
import { PriorityIcon, StateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
@@ -26,10 +26,14 @@ import {
IIssueLabels,
IIssueViewProps,
IState,
TIssuePriorities,
TStateGroups,
UserAuth,
} from "types";
// fetch-keys
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
type Props = {
currentState?: IState | null;
@@ -111,14 +115,27 @@ export const SingleList: React.FC<Props> = ({
switch (selectedGroup) {
case "state":
icon =
currentState && getStateGroupIcon(currentState.group, "16", "16", currentState.color);
icon = currentState && (
<StateGroupIcon
stateGroup={currentState.group}
color={currentState.color}
height="16px"
width="16px"
/>
);
break;
case "state_detail.group":
icon = getStateGroupIcon(groupTitle as any, "16", "16");
icon = (
<StateGroupIcon
stateGroup={groupTitle as TStateGroups}
color={STATE_GROUP_COLORS[groupTitle as TStateGroups]}
height="16px"
width="16px"
/>
);
break;
case "priority":
icon = getPriorityIcon(groupTitle, "text-lg");
icon = <PriorityIcon priority={groupTitle as TIssuePriorities} className="text-lg" />;
break;
case "project":
const project = projects?.find((p) => p.id === groupTitle);
@@ -19,7 +19,7 @@ import { ActiveCycleProgressStats } from "components/cycles";
// icons
import { CalendarDaysIcon } from "@heroicons/react/20/solid";
import { getPriorityIcon } from "components/icons/priority-icon";
import { PriorityIcon } from "components/icons/priority-icon";
import {
TargetIcon,
ContrastIcon,
@@ -28,7 +28,7 @@ import {
TriangleExclamationIcon,
AlarmClockIcon,
LayerDiagonalIcon,
CompletedStateIcon,
StateGroupIcon,
} from "components/icons";
import { StarIcon } from "@heroicons/react/24/outline";
// components
@@ -385,8 +385,8 @@ export const ActiveCycleDetails: React.FC = () => {
<LayerDiagonalIcon className="h-4 w-4 flex-shrink-0" />
{cycle.total_issues} issues
</div>
<div className="flex gap-2">
<CompletedStateIcon width={16} height={16} color="#438AF3" />
<div className="flex items-center gap-2">
<StateGroupIcon stateGroup="completed" height="14px" width="14px" />
{cycle.completed_issues} issues
</div>
</div>
@@ -477,7 +477,7 @@ export const ActiveCycleDetails: React.FC = () => {
: "border-orange-500/20 bg-orange-500/20 text-orange-500"
}`}
>
{getPriorityIcon(issue.priority, "text-sm")}
<PriorityIcon priority={issue.priority} className="text-sm" />
</div>
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
<div className={`flex items-center gap-2 text-custom-text-200`}>
+1 -1
View File
@@ -41,7 +41,7 @@ const IntegrationGuide = () => {
);
const handleCsvClose = () => {
router.replace(`/plane/settings/exports`);
router.replace(`/${workspaceSlug?.toString()}/settings/exports`);
};
return (
@@ -1,21 +0,0 @@
import React from "react";
import type { Props } from "./types";
export const BacklogStateIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "rgb(var(--color-text-200))",
}) => (
<svg
width={width}
height={height}
className={className}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="10" cy="10" r="9" stroke={color} strokeLinecap="round" strokeDasharray="4 4" />
</svg>
);
@@ -1,78 +0,0 @@
import React from "react";
import type { Props } from "./types";
export const CancelledStateIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#f2655a",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 84.36 84.36"
>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
/>
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
<path
className="cls-3"
fill="none"
strokeWidth={3}
stroke="#ffffff"
strokeLinecap="square"
strokeMiterlimit={10}
d="M32.64,32.44q9.54,9.75,19.09,19.48"
/>
<path
className="cls-3"
fill="none"
strokeWidth={3}
stroke="#ffffff"
strokeLinecap="square"
strokeMiterlimit={10}
d="M32.64,51.92,51.73,32.44"
/>
</g>
</g>
</svg>
);
@@ -1,69 +0,0 @@
import React from "react";
import type { Props } from "./types";
export const CompletedStateIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#438af3",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 84.36 84.36"
>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
/>
<path
className="cls-1"
fill="none"
strokeWidth={3}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
/>
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
<path
className="cls-3"
fill="none"
strokeWidth={3}
stroke="#ffffff"
strokeLinecap="square"
strokeMiterlimit={10}
d="M30.45,43.75l6.61,6.61L53.92,34"
/>
</g>
</g>
</svg>
);
+2 -8
View File
@@ -1,6 +1,7 @@
export * from "./module";
export * from "./state";
export * from "./alarm-clock-icon";
export * from "./attachment-icon";
export * from "./backlog-state-icon";
export * from "./blocked-icon";
export * from "./blocker-icon";
export * from "./bolt-icon";
@@ -8,12 +9,10 @@ export * from "./calendar-before-icon";
export * from "./calendar-after-icon";
export * from "./calendar-month-icon";
export * from "./cancel-icon";
export * from "./cancelled-state-icon";
export * from "./clipboard-icon";
export * from "./color-pallette-icon";
export * from "./comment-icon";
export * from "./completed-cycle-icon";
export * from "./completed-state-icon";
export * from "./current-cycle-icon";
export * from "./cycle-icon";
export * from "./discord-icon";
@@ -23,11 +22,9 @@ export * from "./ellipsis-horizontal-icon";
export * from "./external-link-icon";
export * from "./github-icon";
export * from "./heartbeat-icon";
export * from "./started-state-icon";
export * from "./layer-diagonal-icon";
export * from "./lock-icon";
export * from "./menu-icon";
export * from "./module";
export * from "./pencil-scribble-icon";
export * from "./plus-icon";
export * from "./person-running-icon";
@@ -36,11 +33,8 @@ export * from "./question-mark-circle-icon";
export * from "./setting-icon";
export * from "./signal-cellular-icon";
export * from "./stacked-layers-icon";
export * from "./started-state-icon";
export * from "./state-group-icon";
export * from "./tag-icon";
export * from "./tune-icon";
export * from "./unstarted-state-icon";
export * from "./upcoming-cycle-icon";
export * from "./user-group-icon";
export * from "./user-icon-circle";
+22 -19
View File
@@ -1,22 +1,25 @@
export const getPriorityIcon = (priority: string | null, className?: string) => {
// types
import { TIssuePriorities } from "types";
type Props = {
priority: TIssuePriorities | null;
className?: string;
};
export const PriorityIcon: React.FC<Props> = ({ priority, className = "" }) => {
if (!className || className === "") className = "text-xs flex items-center";
priority = priority?.toLowerCase() ?? null;
switch (priority) {
case "urgent":
return <span className={`material-symbols-rounded ${className}`}>error</span>;
case "high":
return <span className={`material-symbols-rounded ${className}`}>signal_cellular_alt</span>;
case "medium":
return (
<span className={`material-symbols-rounded ${className}`}>signal_cellular_alt_2_bar</span>
);
case "low":
return (
<span className={`material-symbols-rounded ${className}`}>signal_cellular_alt_1_bar</span>
);
default:
return <span className={`material-symbols-rounded ${className}`}>block</span>;
}
return (
<span className={`material-symbols-rounded ${className}`}>
{priority === "urgent"
? "error"
: priority === "high"
? "signal_cellular_alt"
: priority === "medium"
? "signal_cellular_alt_2_bar"
: priority === "low"
? "signal_cellular_alt_1_bar"
: "block"}
</span>
);
};
@@ -1,77 +0,0 @@
import React from "react";
import type { Props } from "./types";
export const StartedStateIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#fbb040",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 83.36 83.36"
>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M20,7.19a39.74,39.74,0,0,1,43.43.54"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M76.17,20a39.76,39.76,0,0,1-.53,43.43"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M63.42,76.17A39.78,39.78,0,0,1,20,75.64"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M7.19,63.42A39.75,39.75,0,0,1,7.73,20"
/>
<path
className="cls-2"
fill={color}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M42.32,41.21q9.57-14.45,19.13-28.9a35.8,35.8,0,0,0-39.09,0Z"
/>
<path
className="cls-2"
fill={color}
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M42.32,41.7,61.45,70.6a35.75,35.75,0,0,1-39.09,0Z"
/>
</g>
</g>
</svg>
);
-66
View File
@@ -1,66 +0,0 @@
import {
BacklogStateIcon,
CancelledStateIcon,
CompletedStateIcon,
StartedStateIcon,
UnstartedStateIcon,
} from "components/icons";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
export const getStateGroupIcon = (
stateGroup: "backlog" | "unstarted" | "started" | "completed" | "cancelled",
width = "20",
height = "20",
color?: string
) => {
switch (stateGroup) {
case "backlog":
return (
<BacklogStateIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["backlog"]}
className="flex-shrink-0"
/>
);
case "unstarted":
return (
<UnstartedStateIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["unstarted"]}
className="flex-shrink-0"
/>
);
case "started":
return (
<StartedStateIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["started"]}
className="flex-shrink-0"
/>
);
case "completed":
return (
<CompletedStateIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["completed"]}
className="flex-shrink-0"
/>
);
case "cancelled":
return (
<CancelledStateIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["cancelled"]}
className="flex-shrink-0"
/>
);
default:
return <></>;
}
};
+24
View File
@@ -0,0 +1,24 @@
type Props = {
width?: string;
height?: string;
className?: string;
color?: string;
};
export const StateGroupBacklogIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#a3a3a3",
}) => (
<svg
height={height}
width={width}
className={className}
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="6" cy="6" r="5.6" stroke={color} stroke-width="0.8" stroke-dasharray="4 4" />
</svg>
);
+34
View File
@@ -0,0 +1,34 @@
type Props = {
width?: string;
height?: string;
className?: string;
color?: string;
};
export const StateGroupCancelledIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#ef4444",
}) => (
<svg
height={height}
width={width}
className={className}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_4052_100277)">
<path
d="M8 8.84L10.58 11.42C10.7 11.54 10.84 11.6 11 11.6C11.16 11.6 11.3 11.54 11.42 11.42C11.54 11.3 11.6 11.16 11.6 11C11.6 10.84 11.54 10.7 11.42 10.58L8.84 8L11.42 5.42C11.54 5.3 11.6 5.16 11.6 5C11.6 4.84 11.54 4.7 11.42 4.58C11.3 4.46 11.16 4.4 11 4.4C10.84 4.4 10.7 4.46 10.58 4.58L8 7.16L5.42 4.58C5.3 4.46 5.16 4.4 5 4.4C4.84 4.4 4.7 4.46 4.58 4.58C4.46 4.7 4.4 4.84 4.4 5C4.4 5.16 4.46 5.3 4.58 5.42L7.16 8L4.58 10.58C4.46 10.7 4.4 10.84 4.4 11C4.4 11.16 4.46 11.3 4.58 11.42C4.7 11.54 4.84 11.6 5 11.6C5.16 11.6 5.3 11.54 5.42 11.42L8 8.84ZM8 16C6.90667 16 5.87333 15.79 4.9 15.37C3.92667 14.95 3.07667 14.3767 2.35 13.65C1.62333 12.9233 1.05 12.0733 0.63 11.1C0.21 10.1267 0 9.09333 0 8C0 6.89333 0.21 5.85333 0.63 4.88C1.05 3.90667 1.62333 3.06 2.35 2.34C3.07667 1.62 3.92667 1.05 4.9 0.63C5.87333 0.21 6.90667 0 8 0C9.10667 0 10.1467 0.21 11.12 0.63C12.0933 1.05 12.94 1.62 13.66 2.34C14.38 3.06 14.95 3.90667 15.37 4.88C15.79 5.85333 16 6.89333 16 8C16 9.09333 15.79 10.1267 15.37 11.1C14.95 12.0733 14.38 12.9233 13.66 13.65C12.94 14.3767 12.0933 14.95 11.12 15.37C10.1467 15.79 9.10667 16 8 16ZM8 14.8C9.89333 14.8 11.5 14.1367 12.82 12.81C14.14 11.4833 14.8 9.88 14.8 8C14.8 6.10667 14.14 4.5 12.82 3.18C11.5 1.86 9.89333 1.2 8 1.2C6.12 1.2 4.51667 1.86 3.19 3.18C1.86333 4.5 1.2 6.10667 1.2 8C1.2 9.88 1.86333 11.4833 3.19 12.81C4.51667 14.1367 6.12 14.8 8 14.8Z"
fill={color}
/>
</g>
<defs>
<clipPath id="clip0_4052_100277">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
+27
View File
@@ -0,0 +1,27 @@
type Props = {
width?: string;
height?: string;
className?: string;
color?: string;
};
export const StateGroupCompletedIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#16a34a",
}) => (
<svg
height={height}
width={width}
className={className}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.80486 9.80731L4.84856 7.85103C4.73197 7.73443 4.58542 7.67478 4.4089 7.67208C4.23238 7.66937 4.08312 7.72902 3.96113 7.85103C3.83913 7.97302 3.77814 8.12093 3.77814 8.29474C3.77814 8.46855 3.83913 8.61645 3.96113 8.73844L6.27206 11.0494C6.42428 11.2016 6.60188 11.2777 6.80486 11.2777C7.00782 11.2777 7.18541 11.2016 7.33764 11.0494L12.0227 6.36435C12.1393 6.24776 12.1989 6.10121 12.2016 5.92469C12.2043 5.74817 12.1447 5.59891 12.0227 5.47692C11.9007 5.35493 11.7528 5.29393 11.579 5.29393C11.4051 5.29393 11.2572 5.35493 11.1353 5.47692L6.80486 9.80731ZM8.00141 16C6.89494 16 5.85491 15.79 4.88132 15.3701C3.90772 14.9502 3.06082 14.3803 2.34064 13.6604C1.62044 12.9405 1.05028 12.094 0.63017 11.1208C0.210057 10.1477 0 9.10788 0 8.00141C0 6.89494 0.209966 5.85491 0.629896 4.88132C1.04983 3.90772 1.61972 3.06082 2.33958 2.34064C3.05946 1.62044 3.90598 1.05028 4.87915 0.630171C5.8523 0.210058 6.89212 0 7.99859 0C9.10506 0 10.1451 0.209966 11.1187 0.629897C12.0923 1.04983 12.9392 1.61972 13.6594 2.33959C14.3796 3.05946 14.9497 3.90598 15.3698 4.87915C15.7899 5.8523 16 6.89212 16 7.99859C16 9.10506 15.79 10.1451 15.3701 11.1187C14.9502 12.0923 14.3803 12.9392 13.6604 13.6594C12.9405 14.3796 12.094 14.9497 11.1208 15.3698C10.1477 15.7899 9.10788 16 8.00141 16ZM8 14.7369C9.88071 14.7369 11.4737 14.0842 12.779 12.779C14.0842 11.4737 14.7369 9.88071 14.7369 8C14.7369 6.11929 14.0842 4.52631 12.779 3.22104C11.4737 1.91577 9.88071 1.26314 8 1.26314C6.11929 1.26314 4.52631 1.91577 3.22104 3.22104C1.91577 4.52631 1.26314 6.11929 1.26314 8C1.26314 9.88071 1.91577 11.4737 3.22104 12.779C4.52631 14.0842 6.11929 14.7369 8 14.7369Z"
fill={color}
/>
</svg>
);
+6
View File
@@ -0,0 +1,6 @@
export * from "./backlog";
export * from "./cancelled";
export * from "./completed";
export * from "./started";
export * from "./state-group-icon";
export * from "./unstarted";
+25
View File
@@ -0,0 +1,25 @@
type Props = {
width?: string;
height?: string;
className?: string;
color?: string;
};
export const StateGroupStartedIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#f59e0b",
}) => (
<svg
height={height}
width={width}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 12 12"
fill="none"
>
<circle cx="6" cy="6" r="5.6" stroke={color} stroke-width="0.8" />
<circle cx="6" cy="6" r="3.35" stroke={color} stroke-width="0.8" stroke-dasharray="2.4 2.4" />
</svg>
);
@@ -0,0 +1,74 @@
// icons
import {
StateGroupBacklogIcon,
StateGroupCancelledIcon,
StateGroupCompletedIcon,
StateGroupStartedIcon,
StateGroupUnstartedIcon,
} from "components/icons";
// types
import { TStateGroups } from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
type Props = {
className?: string;
color?: string;
height?: string;
stateGroup: TStateGroups;
width?: string;
};
export const StateGroupIcon: React.FC<Props> = ({
className = "",
color,
height = "12px",
width = "12px",
stateGroup,
}) => {
if (stateGroup === "backlog")
return (
<StateGroupBacklogIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["backlog"]}
className={`flex-shrink-0 ${className}`}
/>
);
else if (stateGroup === "cancelled")
return (
<StateGroupCancelledIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["cancelled"]}
className={`flex-shrink-0 ${className}`}
/>
);
else if (stateGroup === "completed")
return (
<StateGroupCompletedIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["completed"]}
className={`flex-shrink-0 ${className}`}
/>
);
else if (stateGroup === "started")
return (
<StateGroupStartedIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["started"]}
className={`flex-shrink-0 ${className}`}
/>
);
else
return (
<StateGroupUnstartedIcon
width={width}
height={height}
color={color ?? STATE_GROUP_COLORS["unstarted"]}
className={`flex-shrink-0 ${className}`}
/>
);
};
+24
View File
@@ -0,0 +1,24 @@
type Props = {
width?: string;
height?: string;
className?: string;
color?: string;
};
export const StateGroupUnstartedIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "#3a3a3a",
}) => (
<svg
height={height}
width={width}
className={className}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="8" cy="8" r="7.4" stroke={color} stroke-width="1.2" />
</svg>
);
@@ -1,59 +0,0 @@
import React from "react";
import type { Props } from "./types";
export const UnstartedStateIcon: React.FC<Props> = ({
width = "20",
height = "20",
className,
color = "rgb(var(--color-text-200))",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 84.36 84.36"
>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
/>
<path
className="cls-1"
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
/>
</g>
</g>
</svg>
);
+2 -2
View File
@@ -3,7 +3,7 @@ import useInboxView from "hooks/use-inbox-view";
// ui
import { MultiLevelDropdown } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons";
import { PriorityIcon } from "components/icons";
// constants
import { PRIORITIES } from "constants/project";
import { INBOX_STATUS } from "constants/inbox";
@@ -42,7 +42,7 @@ export const FiltersDropdown: React.FC = () => {
id: priority === null ? "null" : priority,
label: (
<div className="flex items-center gap-2 capitalize">
{getPriorityIcon(priority)} {priority ?? "None"}
<PriorityIcon priority={priority} /> {priority ?? "None"}
</div>
),
value: {
+6 -2
View File
@@ -2,9 +2,11 @@
import useInboxView from "hooks/use-inbox-view";
// icons
import { XMarkIcon } from "@heroicons/react/24/outline";
import { getPriorityIcon } from "components/icons";
import { PriorityIcon } from "components/icons";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { TIssuePriorities } from "types";
// constants
import { INBOX_STATUS } from "constants/inbox";
@@ -48,7 +50,9 @@ export const InboxFiltersList = () => {
: "bg-custom-background-90 text-custom-text-200"
}`}
>
<span>{getPriorityIcon(priority)}</span>
<span>
<PriorityIcon priority={priority as TIssuePriorities} />
</span>
<button
type="button"
className="cursor-pointer"
+2 -5
View File
@@ -4,7 +4,7 @@ import Link from "next/link";
// ui
import { Tooltip } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons";
import { PriorityIcon } from "components/icons";
import {
CalendarDaysIcon,
CheckCircleIcon,
@@ -65,10 +65,7 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
: "border-custom-border-200"
}`}
>
{getPriorityIcon(
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
"text-sm"
)}
<PriorityIcon priority={issue.priority ?? null} className="text-sm" />
</div>
</Tooltip>
<Tooltip
+1 -1
View File
@@ -35,7 +35,7 @@ import type { IInboxIssue, IIssue } from "types";
// fetch-keys
import { INBOX_ISSUES, INBOX_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
const defaultValues = {
const defaultValues: Partial<IInboxIssue> = {
name: "",
description_html: "",
estimate_point: null,
+2 -2
View File
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
// ui
import { Tooltip } from "components/ui";
// icons
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { findTotalDaysInRange, renderShortDate } from "helpers/date-time.helper";
// types
@@ -52,7 +52,7 @@ export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => {
className="relative w-full flex items-center gap-2 h-full cursor-pointer"
onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)}
>
{getStateGroupIcon(data?.state_detail?.group, "14", "14", data?.state_detail?.color)}
<StateGroupIcon stateGroup={data?.state_detail?.group} color={data?.state_detail?.color} />
<div className="text-xs text-custom-text-300 flex-shrink-0">
{data?.project_detail?.identifier} {data?.sequence_id}
</div>
@@ -11,11 +11,11 @@ import { DateFilterModal } from "components/core";
// ui
import { MultiLevelDropdown } from "components/ui";
// icons
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
import { PriorityIcon, StateGroupIcon } from "components/icons";
// helpers
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
// types
import { IIssueFilterOptions, IQuery } from "types";
import { IIssueFilterOptions, IQuery, TStateGroups } from "types";
// fetch-keys
import { WORKSPACE_LABELS } from "constants/fetch-keys";
// constants
@@ -61,8 +61,10 @@ export const MyIssuesSelectFilters: React.FC<Props> = ({
<DateFilterModal
title={dateFilterType.title}
field={dateFilterType.type}
isOpen={isDateFilterModalOpen}
filters={filters as IIssueFilterOptions}
handleClose={() => setIsDateFilterModalOpen(false)}
isOpen={isDateFilterModalOpen}
onSelect={onSelect}
/>
)}
<MultiLevelDropdown
@@ -81,7 +83,7 @@ export const MyIssuesSelectFilters: React.FC<Props> = ({
id: priority === null ? "null" : priority,
label: (
<div className="flex items-center gap-2 capitalize">
{getPriorityIcon(priority)} {priority ?? "None"}
<PriorityIcon priority={priority} /> {priority ?? "None"}
</div>
),
value: {
@@ -102,7 +104,7 @@ export const MyIssuesSelectFilters: React.FC<Props> = ({
id: key,
label: (
<div className="flex items-center gap-2">
{getStateGroupIcon(key as any, "16", "16")}{" "}
<StateGroupIcon stateGroup={key as TStateGroups} />
{GROUP_CHOICES[key as keyof typeof GROUP_CHOICES]}
</div>
),
@@ -18,7 +18,7 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
import { orderArrayBy } from "helpers/array.helper";
// types
import { IIssue, IIssueFilterOptions } from "types";
import { IIssue, IIssueFilterOptions, TIssuePriorities } from "types";
// fetch-keys
import { USER_ISSUES, WORKSPACE_LABELS } from "constants/fetch-keys";
import { PlusIcon } from "@heroicons/react/24/outline";
@@ -96,7 +96,7 @@ export const MyIssuesView: React.FC<Props> = ({
const sourceGroup = source.droppableId;
const destinationGroup = destination.droppableId;
draggedItem[groupBy] = destinationGroup;
draggedItem[groupBy] = destinationGroup as TIssuePriorities;
mutate<{
[key: string]: IIssue[];
@@ -2,7 +2,7 @@
import { observer } from "mobx-react-lite";
// headless ui
import { Disclosure } from "@headlessui/react";
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// hooks
import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
@@ -19,7 +19,7 @@ import { CustomDatePicker, Icon } from "components/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { IIssue } from "types";
import { IIssue, TIssuePriorities } from "types";
type Props = {
handleDeleteIssue: () => void;
@@ -66,7 +66,10 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({
{mode === "full" && (
<div className="flex justify-between gap-2 pb-3">
<h6 className="flex items-center gap-2 font-medium">
{getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)}
<StateGroupIcon
stateGroup={issue.state_detail.group}
color={issue.state_detail.color}
/>
{issue.project_detail.identifier}-{issue.sequence_id}
</h6>
<div className="flex items-center gap-2">
@@ -114,7 +117,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({
<div className="w-3/4">
<SidebarPrioritySelect
value={issue.priority}
onChange={(val: string) => handleUpdateIssue({ priority: val })}
onChange={(val) => handleUpdateIssue({ priority: val })}
disabled={readOnly}
/>
</div>
+11 -4
View File
@@ -3,12 +3,14 @@ import React from "react";
// ui
import { CustomSelect } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons/priority-icon";
import { PriorityIcon } from "components/icons/priority-icon";
// types
import { TIssuePriorities } from "types";
// constants
import { PRIORITIES } from "constants/project";
type Props = {
value: string | null;
value: TIssuePriorities;
onChange: (value: string) => void;
};
@@ -18,7 +20,10 @@ export const IssuePrioritySelect: React.FC<Props> = ({ value, onChange }) => (
label={
<div className="flex items-center justify-center gap-2 text-xs">
<span className="flex items-center">
{getPriorityIcon(value, `text-xs ${value ? "" : "text-custom-text-200"}`)}
<PriorityIcon
priority={value}
className={`text-xs ${value ? "" : "text-custom-text-200"}`}
/>
</span>
<span className={`${value ? "" : "text-custom-text-200"} capitalize`}>
{value ?? "Priority"}
@@ -32,7 +37,9 @@ export const IssuePrioritySelect: React.FC<Props> = ({ value, onChange }) => (
<CustomSelect.Option key={priority} value={priority}>
<div className="flex w-full justify-between gap-2 rounded">
<div className="flex items-center justify-start gap-2">
<span>{getPriorityIcon(priority)}</span>
<span>
<PriorityIcon priority={priority} />
</span>
<span className="capitalize">{priority ?? "None"}</span>
</div>
</div>
+7 -4
View File
@@ -10,7 +10,7 @@ import stateService from "services/state.service";
import { CustomSearchSelect } from "components/ui";
// icons
import { PlusIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
// fetch keys
@@ -41,7 +41,7 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
query: state.name,
content: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)}
<StateGroupIcon stateGroup={state.group} color={state.color} />
{state.name}
</div>
),
@@ -58,9 +58,12 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
label={
<div className="flex items-center gap-2">
{selectedOption ? (
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
) : currentDefaultState ? (
getStateGroupIcon(currentDefaultState.group, "16", "16", currentDefaultState.color)
<StateGroupIcon
stateGroup={currentDefaultState.group}
color={currentDefaultState.color}
/>
) : (
<Squares2X2Icon className="h-3.5 w-3.5 text-custom-text-200" />
)}
@@ -3,13 +3,15 @@ import React from "react";
// ui
import { CustomSelect } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons/priority-icon";
import { PriorityIcon } from "components/icons/priority-icon";
// types
import { TIssuePriorities } from "types";
// constants
import { PRIORITIES } from "constants/project";
type Props = {
value: string | null;
onChange: (val: string) => void;
value: TIssuePriorities;
onChange: (val: TIssuePriorities) => void;
disabled?: boolean;
};
@@ -31,7 +33,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
}`}
>
<span className="grid place-items-center -my-1">
{getPriorityIcon(value ?? "None", "!text-sm")}
<PriorityIcon priority={value} className="!text-sm" />
</span>
<span>{value ?? "None"}</span>
</button>
@@ -44,7 +46,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
{PRIORITIES.map((option) => (
<CustomSelect.Option key={option} value={option} className="capitalize">
<>
{getPriorityIcon(option, "text-sm")}
<PriorityIcon priority={option} className="text-sm" />
{option ?? "None"}
</>
</CustomSelect.Option>
@@ -9,7 +9,7 @@ import stateService from "services/state.service";
// ui
import { Spinner, CustomSelect } from "components/ui";
// icons
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper";
@@ -42,17 +42,12 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
<button type="button" className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
{selectedState ? (
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
{getStateGroupIcon(
selectedState?.group ?? "backlog",
"14",
"14",
selectedState?.color ?? ""
)}
<StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} />
{addSpaceIfCamelCase(selectedState?.name ?? "")}
</div>
) : inboxIssueId ? (
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
{getStateGroupIcon("backlog", "14", "14", "#ff7700")}
<StateGroupIcon stateGroup="backlog" color="#ff7700" />
Triage
</div>
) : (
@@ -71,7 +66,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
states.map((state) => (
<CustomSelect.Option key={state.id} value={state.id}>
<>
{getStateGroupIcon(state.group, "16", "16", state.color)}
<StateGroupIcon stateGroup={state.group} color={state.color} />
{state.name}
</>
</CustomSelect.Option>
+1 -1
View File
@@ -401,7 +401,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
render={({ field: { value } }) => (
<SidebarPrioritySelect
value={value}
onChange={(val: string) => submitChanges({ priority: val })}
onChange={(val) => submitChanges({ priority: val })}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
/>
)}
+14 -18
View File
@@ -2,18 +2,18 @@ import React from "react";
import { useRouter } from "next/router";
// services
import trackEventServices from "services/track-event.service";
// ui
import { CustomSelect, Tooltip } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons/priority-icon";
import { PriorityIcon } from "components/icons/priority-icon";
// helpers
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { ICurrentUserResponse, IIssue } from "types";
import { ICurrentUserResponse, IIssue, TIssuePriorities } from "types";
// constants
import { PRIORITIES } from "constants/project";
// services
import trackEventServices from "services/track-event.service";
// helper
import { capitalizeFirstLetter } from "helpers/string.helper";
type Props = {
issue: IIssue;
@@ -42,7 +42,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
return (
<CustomSelect
value={issue.priority}
onChange={(data: string) => {
onChange={(data: TIssuePriorities) => {
partialUpdateIssue({ priority: data }, issue);
trackEventServices.trackIssuePartialPropertyUpdateEvent(
{
@@ -77,9 +77,9 @@ export const ViewPrioritySelect: React.FC<Props> = ({
position={tooltipPosition}
>
<span className="flex gap-1 items-center text-custom-text-200 text-xs">
{getPriorityIcon(
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
`text-sm ${
<PriorityIcon
priority={issue.priority}
className={`text-sm ${
issue.priority === "urgent"
? "text-white"
: issue.priority === "high"
@@ -89,13 +89,9 @@ export const ViewPrioritySelect: React.FC<Props> = ({
: issue.priority === "low"
? "text-green-500"
: "text-custom-text-200"
}`
)}
{noBorder
? issue.priority && issue.priority !== ""
? capitalizeFirstLetter(issue.priority) ?? ""
: "None"
: ""}
}`}
/>
{noBorder ? capitalizeFirstLetter(issue.priority ?? "None") : ""}
</span>
</Tooltip>
</button>
@@ -108,7 +104,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
{PRIORITIES?.map((priority) => (
<CustomSelect.Option key={priority} value={priority} className="capitalize">
<>
{getPriorityIcon(priority, "text-sm")}
<PriorityIcon priority={priority} className="text-sm" />
{priority ?? "None"}
</>
</CustomSelect.Option>
+5 -4
View File
@@ -10,7 +10,7 @@ import trackEventServices from "services/track-event.service";
// ui
import { CustomSearchSelect, Tooltip } from "components/ui";
// icons
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
@@ -59,7 +59,7 @@ export const ViewStateSelect: React.FC<Props> = ({
query: state.name,
content: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)}
<StateGroupIcon stateGroup={state.group} color={state.color} />
{state.name}
</div>
),
@@ -75,8 +75,9 @@ export const ViewStateSelect: React.FC<Props> = ({
>
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
<span className="h-3.5 w-3.5">
{selectedOption &&
getStateGroupIcon(selectedOption.group, "14", "14", selectedOption.color)}
{selectedOption && (
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
)}
</span>
<span className="truncate">{selectedOption?.name ?? "State"}</span>
</div>
@@ -18,7 +18,7 @@ import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
import { orderArrayBy } from "helpers/array.helper";
// types
import { IIssue, IIssueFilterOptions } from "types";
import { IIssue, IIssueFilterOptions, TIssuePriorities } from "types";
// fetch-keys
import { USER_PROFILE_PROJECT_SEGREGATION, WORKSPACE_LABELS } from "constants/fetch-keys";
@@ -108,7 +108,7 @@ export const ProfileIssuesView = () => {
const sourceGroup = source.droppableId;
const destinationGroup = destination.droppableId;
draggedItem[groupByProperty] = destinationGroup;
draggedItem[groupByProperty] = destinationGroup as TIssuePriorities;
mutateProfileIssues((prevData: any) => {
if (!prevData) return prevData;
+2 -2
View File
@@ -15,7 +15,7 @@ import {
PencilSquareIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
// helpers
import { addSpaceIfCamelCase } from "helpers/string.helper";
import { groupBy, orderArrayBy } from "helpers/array.helper";
@@ -162,7 +162,7 @@ export const SingleState: React.FC<Props> = ({
return (
<div className="group flex items-center justify-between gap-2 border-custom-border-200 bg-custom-background-100 p-5 first:rounded-t-[10px] last:rounded-b-[10px]">
<div className="flex items-center gap-3">
{getStateGroupIcon(state.group, "20", "20", state.color)}
<StateGroupIcon stateGroup={state.group} color={state.color} height="20px" width="20px" />
<div>
<h6 className="text-sm">{addSpaceIfCamelCase(state.name)}</h6>
<p className="text-xs text-custom-text-200">{state.description}</p>
+4 -3
View File
@@ -1,9 +1,10 @@
import { useImperativeHandle, useRef, forwardRef, useEffect } from "react";
import { useEditor, EditorContent, Editor } from "@tiptap/react";
import { useDebouncedCallback } from "use-debounce";
// components
import { EditorBubbleMenu } from "./bubble-menu";
import { TiptapExtensions } from "./extensions";
import { TiptapEditorProps } from "./props";
import { useImperativeHandle, useRef, forwardRef } from "react";
import { ImageResizer } from "./extensions/image-resize";
import { TableMenu } from "./table-menu";
@@ -76,8 +77,8 @@ const Tiptap = (props: ITipTapRichTextEditor) => {
const editorClassNames = `relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md
${noBorder ? "" : "border border-custom-border-200"} ${
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
} ${customClassName}`;
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
} ${customClassName}`;
if (!editor) return null;
editorRef.current = editor;
+8 -4
View File
@@ -13,7 +13,7 @@ import { DateFilterModal } from "components/core";
// ui
import { Avatar, MultiLevelDropdown } from "components/ui";
// icons
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
import { PriorityIcon, StateGroupIcon } from "components/icons";
// helpers
import { getStatesList } from "helpers/state.helper";
import { checkIfArraysHaveSameElements } from "helpers/array.helper";
@@ -78,8 +78,10 @@ export const SelectFilters: React.FC<Props> = ({
<DateFilterModal
title={dateFilterType.title}
field={dateFilterType.type}
isOpen={isDateFilterModalOpen}
filters={filters as IIssueFilterOptions}
handleClose={() => setIsDateFilterModalOpen(false)}
isOpen={isDateFilterModalOpen}
onSelect={onSelect}
/>
)}
<MultiLevelDropdown
@@ -97,7 +99,8 @@ export const SelectFilters: React.FC<Props> = ({
id: priority === null ? "null" : priority,
label: (
<div className="flex items-center gap-2 capitalize">
{getPriorityIcon(priority)} {priority ?? "None"}
<PriorityIcon priority={priority} />
{priority ?? "None"}
</div>
),
value: {
@@ -116,7 +119,8 @@ export const SelectFilters: React.FC<Props> = ({
id: state.id,
label: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)} {state.name}
<StateGroupIcon stateGroup={state.group} color={state.color} />
{state.name}
</div>
),
value: {
@@ -0,0 +1,419 @@
import { useRouter } from "next/router";
// icons
import { Icon, Tooltip } from "components/ui";
import { Squares2X2Icon } from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon } from "components/icons";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { IIssueActivity } from "types";
const IssueLink = ({ activity }: { activity: IIssueActivity }) => (
<Tooltip
tooltipContent={
activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"
}
>
<button
type="button"
onClick={() =>
console.log(
"issue",
JSON.stringify({
project_id: activity.project,
issue_id: activity.issue,
})
)
}
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.issue_detail
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
: "Issue"}
<Icon iconName="launch" className="!text-xs" />
</button>
</Tooltip>
);
const UserLink = ({ activity }: { activity: IIssueActivity }) => (
<button
type="button"
onClick={() => {
console.log("user", activity.actor);
}}
className="font-medium text-custom-text-100 inline-flex items-center hover:underline"
>
{activity.new_value && activity.new_value !== "" ? activity.new_value : activity.old_value}
</button>
);
const activityDetails: {
[key: string]: {
message: (
activity: IIssueActivity,
showIssue: boolean,
workspaceSlug: string
) => React.ReactNode;
icon: React.ReactNode;
};
} = {
assignees: {
message: (activity, showIssue) => (
<>
{activity.old_value === "" ? "added a new assignee " : "removed the assignee "}
<UserLink activity={activity} />
{showIssue && (
<>
{" "}
to <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="group" className="!text-sm" aria-hidden="true" />,
},
archived_at: {
message: (activity) => {
if (activity.new_value === "restore") return "restored the issue.";
else return "archived the issue.";
},
icon: <Icon iconName="archive" className="!text-sm" aria-hidden="true" />,
},
attachment: {
message: (activity, showIssue) => (
<>
{activity.verb === "created" ? "uploaded a new " : "removed an "}
{activity.new_value && activity.new_value !== "" ? (
<button type="button" onClick={() => console.log("attachment", activity.new_value)}>
attachment
</button>
) : (
"attachment"
)}
{showIssue && activity.verb === "created" ? " to " : " from "}
{showIssue && <IssueLink activity={activity} />}
</>
),
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
},
blocking: {
message: (activity) => (
<>
{activity.old_value === ""
? "marked this issue is blocking issue "
: "removed the blocking issue "}
<span className="font-medium text-custom-text-100">
{activity.old_value === "" ? activity.new_value : activity.old_value}
</span>
.
</>
),
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
},
blocks: {
message: (activity) => (
<>
{activity.old_value === ""
? "marked this issue is being blocked by issue "
: "removed this issue being blocked by issue "}
<span className="font-medium text-custom-text-100">
{activity.old_value === "" ? activity.new_value : activity.old_value}
</span>
.
</>
),
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
},
cycles: {
message: (activity) => (
<>
{activity.verb === "created" && "added this issue to the cycle "}
{activity.verb === "updated" && "set the cycle to "}
{activity.verb === "deleted" && "removed the issue from the cycle "}
<button
type="button"
onClick={() =>
console.log(
"cycle",
JSON.stringify({
cycle_id: activity.new_identifier,
project_id: activity.project,
})
)
}
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.new_value}
<Icon iconName="launch" className="!text-xs" />
</button>
</>
),
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
},
description: {
message: (activity, showIssue) => (
<>
updated the description
{showIssue && (
<>
{" "}
of <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
},
estimate_point: {
message: (activity, showIssue) => (
<>
{activity.new_value ? "set the estimate point to " : "removed the estimate point "}
{activity.new_value && (
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
)}
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
</>
),
icon: <Icon iconName="change_history" className="!text-sm" aria-hidden="true" />,
},
issue: {
message: (activity) => {
if (activity.verb === "created") return "created the issue.";
else return "deleted an issue.";
},
icon: <Icon iconName="stack" className="!text-sm" aria-hidden="true" />,
},
labels: {
message: (activity, showIssue) => (
<>
{activity.old_value === "" ? "added a new label " : "removed the label "}
<span className="inline-flex items-center gap-3 rounded-full border border-custom-border-300 px-2 py-0.5 text-xs">
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: "#000000",
}}
aria-hidden="true"
/>
<span className="font-medium text-custom-text-100">
{activity.old_value === "" ? activity.new_value : activity.old_value}
</span>
</span>
{showIssue && (
<>
{" "}
to <IssueLink activity={activity} />
</>
)}
</>
),
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
},
link: {
message: (activity, showIssue) => (
<>
{activity.verb === "created" && "added this "}
{activity.verb === "updated" && "updated this "}
{activity.verb === "deleted" && "removed this "}
<button
onClick={() =>
console.log(
"link",
activity.verb === "created" ? activity.new_value : activity.old_value
)
}
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
link
<Icon iconName="launch" className="!text-xs" />
</button>
{showIssue && (
<>
{" "}
to <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="link" className="!text-sm" aria-hidden="true" />,
},
modules: {
message: (activity) => (
<>
{activity.verb === "created" && "added this "}
{activity.verb === "updated" && "updated this "}
{activity.verb === "deleted" && "removed this "}
<button
onClick={() =>
console.log(
"module",
activity.verb === "created" ? activity.new_value : activity.old_value
)
}
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
module
<Icon iconName="launch" className="!text-xs" />
</button>
.
</>
),
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
},
name: {
message: (activity, showIssue) => (
<>
set the name to {activity.new_value}
{showIssue && (
<>
{" "}
of <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
},
parent: {
message: (activity, showIssue) => (
<>
{activity.new_value ? "set the parent to " : "removed the parent "}
<span className="font-medium text-custom-text-100">
{activity.new_value ? activity.new_value : activity.old_value}
</span>
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
},
priority: {
message: (activity, showIssue) => (
<>
set the priority to{" "}
<span className="font-medium text-custom-text-100">
{activity.new_value ? capitalizeFirstLetter(activity.new_value) : "None"}
</span>
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
},
start_date: {
message: (activity, showIssue) => (
<>
{activity.new_value ? "set the start date to " : "removed the start date "}
<span className="font-medium text-custom-text-100">
{activity.new_value ? renderShortDateWithYearFormat(activity.new_value) : "None"}
</span>
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
</>
),
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
},
state: {
message: (activity, showIssue) => (
<>
set the state to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
.
</>
),
icon: <Squares2X2Icon className="h-3 w-3" aria-hidden="true" />,
},
target_date: {
message: (activity, showIssue) => (
<>
{activity.new_value ? "set the target date to " : "removed the target date "}
{activity.new_value && (
<span className="font-medium text-custom-text-100">
{renderShortDateWithYearFormat(activity.new_value)}
</span>
)}
{showIssue && (
<>
{" "}
for <IssueLink activity={activity} />
</>
)}
</>
),
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
},
};
export const ActivityIcon = ({ activity }: { activity: IIssueActivity }) => (
<>{activityDetails[activity.field as keyof typeof activityDetails]?.icon}</>
);
export const ActivityMessage = ({
activity,
showIssue = false,
}: {
activity: IIssueActivity;
showIssue?: boolean;
}) => {
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<>
{activityDetails[activity.field as keyof typeof activityDetails]?.message(
activity,
showIssue,
workspaceSlug?.toString() ?? ""
)}
</>
);
};
+5 -1
View File
@@ -120,7 +120,11 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit }) => {
</div>
<div className="inline">
<PrimaryButton type="submit" disabled={isSubmitting || disabled} className="mt-2">
<PrimaryButton
type="submit"
disabled={isSubmitting || disabled}
className="mt-2 w-10 h-10 flex items-center justify-center"
>
<Send className="w-4 h-4" />
</PrimaryButton>
</div>
+4
View File
@@ -12,3 +12,7 @@ export * from "./issue-activity";
export * from "./select-assignee";
export * from "./select-estimate";
export * from "./add-comment";
export * from "./select-parent";
export * from "./select-blocker";
export * from "./select-blocked";
export * from "./activity-message";
+17 -18
View File
@@ -2,7 +2,6 @@
import React from "react";
// next
import Link from "next/link";
import { useRouter } from "next/router";
// swr
@@ -16,12 +15,10 @@ import issuesService from "services/issues.service";
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
// components
import { Label, AddComment } from "components/web-view";
import { CommentCard } from "components/issues/comment";
import { ActivityIcon, ActivityMessage } from "components/core";
import { Label, AddComment, ActivityMessage, ActivityIcon } from "components/web-view";
// helpers
import { timeAgo } from "helpers/date-time.helper";
@@ -44,7 +41,6 @@ export const IssueActivity: React.FC<Props> = (props) => {
const { workspaceSlug, projectId, issueId } = router.query;
const { user } = useUser();
const { setToastAlert } = useToast();
const { data: issueActivities, mutate: mutateIssueActivity } = useSWR(
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null,
@@ -104,11 +100,14 @@ export const IssueActivity: React.FC<Props> = (props) => {
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
})
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Comment could not be posted. Please try again.",
})
console.log(
"toast",
JSON.stringify({
type: "error",
title: "Error!",
message: "Comment could not be posted. Please try again.",
})
)
);
};
@@ -181,15 +180,15 @@ export const IssueActivity: React.FC<Props> = (props) => {
{activityItem.actor_detail.first_name} Bot
</span>
) : (
<Link
href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}
<button
type="button"
className="text-gray font-medium"
onClick={() => console.log("user", activityItem.actor)}
>
<a className="text-gray font-medium">
{activityItem.actor_detail.is_bot
? activityItem.actor_detail.first_name
: activityItem.actor_detail.display_name}
</a>
</Link>
{activityItem.actor_detail.is_bot
? activityItem.actor_detail.first_name
: activityItem.actor_detail.display_name}
</button>
)}{" "}
{message}{" "}
<span className="whitespace-nowrap">
+31 -22
View File
@@ -17,11 +17,8 @@ import { useDropzone } from "react-dropzone";
// fetch key
import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
// hooks
import useToast from "hooks/use-toast";
// icons
import { ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { FileText, ChevronRight, X, Image as ImageIcon } from "lucide-react";
// components
import { Label, WebViewModal } from "components/web-view";
@@ -34,6 +31,8 @@ type Props = {
allowed: boolean;
};
const isImage = (fileName: string) => /\.(gif|jpe?g|tiff?|png|webp|bmp)$/i.test(fileName);
export const IssueAttachments: React.FC<Props> = (props) => {
const { allowed } = props;
@@ -46,8 +45,6 @@ export const IssueAttachments: React.FC<Props> = (props) => {
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
const { setToastAlert } = useToast();
const onDrop = useCallback(
(acceptedFiles: File[]) => {
if (!acceptedFiles[0] || !workspaceSlug) return;
@@ -77,23 +74,30 @@ export const IssueAttachments: React.FC<Props> = (props) => {
false
);
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
setToastAlert({
type: "success",
title: "Success!",
message: "File added successfully.",
});
console.log(
"toast",
JSON.stringify({
type: "success",
title: "Success!",
message: "File added successfully.",
})
);
setIsOpen(false);
setIsLoading(false);
})
.catch((err) => {
setIsLoading(false);
setToastAlert({
type: "error",
title: "error!",
message: "Something went wrong. please check file type & size (max 5 MB)",
});
console.log(
"toast",
JSON.stringify({
type: "error",
title: "error!",
message: "Something went wrong. please check file type & size (max 5 MB)",
})
);
});
},
[issueId, projectId, setToastAlert, workspaceSlug]
[issueId, projectId, workspaceSlug]
);
const { getRootProps, getInputProps } = useDropzone({
@@ -136,7 +140,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
) : (
<>
<h3 className="text-lg">Upload</h3>
<ChevronRightIcon className="w-5 h-5" />
<ChevronRight className="w-5 h-5" />
</>
)}
</div>
@@ -151,8 +155,13 @@ export const IssueAttachments: React.FC<Props> = (props) => {
className="px-3 border border-custom-border-200 rounded-[4px] py-2 flex justify-between items-center bg-custom-background-100"
>
<Link href={attachment.asset}>
<a target="_blank" className="text-custom-text-200 truncate">
{attachment.attributes.name}
<a target="_blank" className="text-custom-text-200 truncate flex items-center">
{isImage(attachment.attributes.name) ? (
<ImageIcon className="w-5 h-5 mr-2 flex-shrink-0 text-custom-text-400" />
) : (
<FileText className="w-5 h-5 mr-2 flex-shrink-0 text-custom-text-400" />
)}
<span className="truncate">{attachment.attributes.name}</span>
</a>
</Link>
{allowed && (
@@ -163,7 +172,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
setAttachmentDeleteModal(true);
}}
>
<XMarkIcon className="w-5 h-5 text-custom-text-100" />
<X className="w-[18px] h-[18px] text-custom-text-400" />
</button>
)}
</div>
@@ -171,7 +180,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
<button
type="button"
onClick={() => setIsOpen(true)}
className="bg-custom-primary-100/10 border border-dotted border-custom-primary-100 text-center py-2 w-full text-custom-primary-100"
className="bg-custom-primary-100/10 border border-dotted rounded-[4px] border-custom-primary-100 text-center py-2 w-full text-custom-primary-100"
>
Click to upload file here
</button>
+5 -4
View File
@@ -12,7 +12,8 @@ import { mutate } from "swr";
import issuesService from "services/issues.service";
// icons
import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
// import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Link as LinkIcon, Plus, Pencil, X } from "lucide-react";
// components
import { Label, WebViewModal, CreateUpdateLinkForm } from "components/web-view";
@@ -108,7 +109,7 @@ export const IssueLinks: React.FC<Props> = (props) => {
setSelectedLink(link.id);
}}
>
<PencilIcon className="w-5 h-5 text-custom-text-100" />
<Pencil className="w-[18px] h-[18px] text-custom-text-400" />
</button>
<button
type="button"
@@ -116,7 +117,7 @@ export const IssueLinks: React.FC<Props> = (props) => {
handleDeleteLink(link.id);
}}
>
<TrashIcon className="w-5 h-5 text-red-500 hover:bg-red-500/20" />
<X className="w-[18px] h-[18px] text-custom-text-400" />
</button>
</div>
)}
@@ -128,7 +129,7 @@ export const IssueLinks: React.FC<Props> = (props) => {
onClick={() => setIsOpen(true)}
className="w-full !py-2 text-custom-text-300 !text-base flex items-center justify-center"
>
<PlusIcon className="w-4 h-4 inline-block mr-1" />
<Plus className="w-[18px] h-[18px] inline-block mr-1" />
<span>Add</span>
</SecondaryButton>
</div>
@@ -1,17 +1,21 @@
// react
import React, { useState } from "react";
// next
import { useRouter } from "next/router";
// react hook forms
import { Control, Controller } from "react-hook-form";
import { Control, Controller, useWatch } from "react-hook-form";
// icons
import { ChevronDownIcon, PlayIcon } from "lucide-react";
import { BlockedIcon, BlockerIcon } from "components/icons";
import { ChevronDown, PlayIcon, User, X, CalendarDays, LayoutGrid, Users } from "lucide-react";
// hooks
import useEstimateOption from "hooks/use-estimate-option";
// ui
import { Icon, SecondaryButton } from "components/ui";
import { SecondaryButton, CustomDatePicker } from "components/ui";
// components
import {
@@ -20,6 +24,8 @@ import {
PrioritySelect,
AssigneeSelect,
EstimateSelect,
ParentSelect,
BlockerSelect,
} from "components/web-view";
// types
@@ -33,6 +39,24 @@ type Props = {
export const IssuePropertiesDetail: React.FC<Props> = (props) => {
const { control, submitChanges } = props;
const blockerIssue = useWatch({
control,
name: "blocker_issues",
});
const blockedIssue = useWatch({
control,
name: "blocked_issues",
});
const startDate = useWatch({
control,
name: "start_date",
});
const router = useRouter();
const { workspaceSlug } = router.query;
const [isViewAllOpen, setIsViewAllOpen] = useState(false);
const { isEstimateActive } = useEstimateOption();
@@ -43,8 +67,8 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
<div className="flex items-center gap-1">
<Icon iconName="grid_view" />
<span className="text-sm text-custom-text-200">State</span>
<LayoutGrid className="h-4 w-4 flex-shrink-0 text-custom-text-400" />
<span className="text-sm text-custom-text-400">State</span>
</div>
<div>
<Controller
@@ -63,8 +87,20 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
<div className="flex items-center gap-1">
<Icon iconName="signal_cellular_alt" />
<span className="text-sm text-custom-text-200">Priority</span>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.5862 14.5239C13.3459 14.5239 13.1416 14.4398 12.9733 14.2715C12.805 14.1032 12.7209 13.8989 12.7209 13.6585V3.76429C12.7209 3.52391 12.805 3.31958 12.9733 3.15132C13.1416 2.98306 13.3459 2.89893 13.5862 2.89893C13.8266 2.89893 14.031 2.98306 14.1992 3.15132C14.3675 3.31958 14.4516 3.52391 14.4516 3.76429V13.6585C14.4516 13.8989 14.3675 14.1032 14.1992 14.2715C14.031 14.4398 13.8266 14.5239 13.5862 14.5239ZM5.1629 14.5239C5.04676 14.5239 4.93557 14.5018 4.82932 14.4576C4.72308 14.4133 4.63006 14.3513 4.55025 14.2715C4.47045 14.1917 4.40843 14.0986 4.36419 13.9922C4.31996 13.8858 4.29785 13.7746 4.29785 13.6585V11.2643C4.29785 11.0239 4.38198 10.8196 4.55025 10.6513C4.71851 10.4831 4.92283 10.3989 5.16322 10.3989C5.40359 10.3989 5.60791 10.4831 5.77618 10.6513C5.94445 10.8196 6.02859 11.0239 6.02859 11.2643V13.6585C6.02859 13.7746 6.00647 13.8858 5.96223 13.9922C5.91801 14.0986 5.85599 14.1917 5.77618 14.2715C5.69638 14.3513 5.60325 14.4133 5.49678 14.4576C5.39033 14.5018 5.27904 14.5239 5.1629 14.5239ZM9.37473 14.5239C9.13436 14.5239 8.93003 14.4398 8.76176 14.2715C8.59349 14.1032 8.50936 13.8989 8.50936 13.6585V7.5143C8.50936 7.27391 8.59349 7.06958 8.76176 6.90132C8.93003 6.73306 9.13436 6.64893 9.37473 6.64893C9.61511 6.64893 9.81943 6.73306 9.98771 6.90132C10.156 7.06958 10.2401 7.27391 10.2401 7.5143V13.6585C10.2401 13.8989 10.156 14.1032 9.98771 14.2715C9.81943 14.4398 9.61511 14.5239 9.37473 14.5239Z"
fill="#A3A3A3"
/>
</svg>
<span className="text-sm text-custom-text-400">Priority</span>
</div>
<div>
<Controller
@@ -73,7 +109,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
render={({ field: { value } }) => (
<PrioritySelect
value={value}
onChange={(val: string) => submitChanges({ priority: val })}
onChange={(val) => submitChanges({ priority: val })}
/>
)}
/>
@@ -83,8 +119,8 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
<div className="flex items-center gap-1">
<Icon iconName="person" />
<span className="text-sm text-custom-text-200">Assignee</span>
<Users className="h-4 w-4 flex-shrink-0 text-custom-text-400" />
<span className="text-sm text-custom-text-400">Assignee</span>
</div>
<div>
<Controller
@@ -106,8 +142,8 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
<div className="flex items-center gap-1">
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90" />
<span className="text-sm text-custom-text-200">Estimate</span>
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90 text-custom-text-400" />
<span className="text-sm text-custom-text-400">Estimate</span>
</div>
<div>
<Controller
@@ -124,6 +160,179 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
</div>
</div>
)}
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
<div className="flex items-center gap-1">
<User className="h-4 w-4 flex-shrink-0 text-custom-text-400" />
<span className="text-sm text-custom-text-400">Parent</span>
</div>
<div>
<Controller
control={control}
name="parent"
render={({ field: { value } }) => (
<ParentSelect
value={value}
onChange={(val) => submitChanges({ parent: val })}
/>
)}
/>
</div>
</div>
</div>
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2">
<div className="flex justify-between items-center">
<div className="flex items-center gap-1">
<BlockerIcon height={16} width={16} />
<span className="text-sm text-custom-text-400">Blocking</span>
</div>
<div>
<Controller
control={control}
name="blocker_issues"
render={({ field: { value } }) => (
<BlockerSelect
value={value}
onChange={(val) =>
submitChanges({
blocker_issues: val,
blockers_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""),
})
}
/>
)}
/>
</div>
</div>
{blockerIssue &&
blockerIssue.map((issue) => (
<div
key={issue.blocker_issue_detail?.id}
className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
>
<a
href={`/${workspaceSlug}/projects/${issue.blocker_issue_detail?.project_detail.id}/issues/${issue.blocker_issue_detail?.id}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1"
>
<BlockerIcon height={10} width={10} />
{`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`}
</a>
<button
type="button"
className="duration-300"
onClick={() => {
const updatedBlockers = blockerIssue.filter(
(i) => i.blocker_issue_detail?.id !== issue.blocker_issue_detail?.id
);
submitChanges({
blocker_issues: updatedBlockers,
blockers_list: updatedBlockers.map(
(i) => i.blocker_issue_detail?.id ?? ""
),
});
}}
>
<X className="h-2 w-2" />
</button>
</div>
))}
</div>
</div>
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2">
<div className="flex justify-between items-center">
<div className="flex items-center gap-1">
<BlockedIcon height={16} width={16} />
<span className="text-sm text-custom-text-400">Blocked by</span>
</div>
<div>
<Controller
control={control}
name="blocked_issues"
render={({ field: { value } }) => (
<BlockerSelect
value={value}
onChange={(val) =>
submitChanges({
blocked_issues: val,
blocks_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""),
})
}
/>
)}
/>
</div>
</div>
{blockedIssue &&
blockedIssue.map((issue) => (
<div
key={issue.blocked_issue_detail?.id}
className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
>
<a
href={`/${workspaceSlug}/projects/${issue.blocked_issue_detail?.project_detail.id}/issues/${issue.blocked_issue_detail?.id}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1"
>
<BlockedIcon height={10} width={10} />
{`${issue?.blocked_issue_detail?.project_detail?.identifier}-${issue?.blocked_issue_detail?.sequence_id}`}
</a>
<button
type="button"
className="duration-300"
onClick={() => {
const updatedBlocked = blockedIssue.filter(
(i) => i.blocked_issue_detail?.id !== issue.blocked_issue_detail?.id
);
submitChanges({
blocked_issues: updatedBlocked,
blocks_list: updatedBlocked.map((i) => i.blocked_issue_detail?.id ?? ""),
});
}}
>
<X className="h-2 w-2" />
</button>
</div>
))}
</div>
</div>
<div className="mb-[6px]">
<div className="border border-custom-border-200 rounded-[4px] p-2">
<div className="flex justify-between items-center">
<div className="flex items-center gap-1">
<CalendarDays className="w-4 h-4 text-custom-text-400" />
<span className="text-sm text-custom-text-400">Due date</span>
</div>
<div>
<Controller
control={control}
name="target_date"
render={({ field: { value } }) => (
<CustomDatePicker
placeholder="Due date"
value={value}
wrapperClassName="w-full"
onChange={(val) =>
submitChanges({
target_date: val,
})
}
className="border-transparent !shadow-none !w-[6.75rem]"
minDate={startDate ? new Date(startDate) : undefined}
/>
)}
/>
</div>
</div>
</div>
</div>
</>
)}
<div className="mb-[6px]">
@@ -135,7 +344,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
<span className="text-base text-custom-primary-100">
{isViewAllOpen ? "View less" : "View all"}
</span>
<ChevronDownIcon
<ChevronDown
size={16}
className={`ml-1 text-custom-primary-100 ${isViewAllOpen ? "-rotate-180" : ""}`}
/>
@@ -90,7 +90,7 @@ export const IssueWebViewForm: React.FC<Props> = (props) => {
debouncedTitleSave();
}}
required={true}
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-xl outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
className="min-h-10 block w-full resize-none overflow-hidden rounded border bg-transparent px-3 py-2 text-xl outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
role="textbox"
disabled={!isAllowed}
/>
+2 -3
View File
@@ -8,7 +8,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { ChevronDown } from "lucide-react";
// services
import projectService from "services/project.service";
@@ -87,8 +87,7 @@ export const AssigneeSelect: React.FC<Props> = (props) => {
) : (
"No assignees"
)}
{/* {selectedAssignee?.member.display_name || "Select assignee"} */}
<ChevronDownIcon className="w-5 h-5" />
<ChevronDown className="w-5 h-5" />
</button>
</>
);
@@ -0,0 +1,87 @@
// react
import React, { useState } from "react";
// next
import { useRouter } from "next/router";
// hooks
import useToast from "hooks/use-toast";
// icons
import { ChevronDown } from "lucide-react";
// components
import { ExistingIssuesListModal } from "components/core";
// types
import { BlockeIssueDetail, ISearchIssueResponse } from "types";
type Props = {
value: any;
onChange: (value: any) => void;
disabled?: boolean;
};
export const BlockedSelect: React.FC<Props> = (props) => {
const { value, onChange, disabled = false } = props;
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
const router = useRouter();
const { issueId } = router.query;
const { setToastAlert } = useToast();
const onSubmit = async (data: ISearchIssueResponse[]) => {
if (data.length === 0) {
setToastAlert({
type: "error",
title: "Error!",
message: "Please select at least one issue.",
});
return;
}
const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({
blocker_issue_detail: {
id: i.id,
name: i.name,
sequence_id: i.sequence_id,
project_detail: {
id: i.project_id,
identifier: i.project__identifier,
name: i.project__name,
},
},
}));
onChange([...(value || []), ...selectedIssues]);
setIsBlockedModalOpen(false);
};
return (
<>
<ExistingIssuesListModal
isOpen={isBlockedModalOpen}
handleClose={() => setIsBlockedModalOpen(false)}
searchParams={{ blocker_blocked_by: true, issue_id: issueId!.toString() }}
handleOnSubmit={onSubmit}
workspaceLevelToggle
/>
<button
type="button"
disabled={disabled}
onClick={() => setIsBlockedModalOpen(true)}
className={
"relative w-full px-2.5 py-0.5 text-base flex justify-between items-center gap-0.5 text-custom-text-100"
}
>
<span className="text-custom-text-200">Select issue</span>
<ChevronDown className="w-4 h-4 text-custom-text-200" />
</button>
</>
);
};
@@ -0,0 +1,87 @@
// react
import React, { useState } from "react";
// next
import { useRouter } from "next/router";
// hooks
import useToast from "hooks/use-toast";
// icons
import { ChevronDown } from "lucide-react";
// components
import { ExistingIssuesListModal } from "components/core";
// types
import { BlockeIssueDetail, ISearchIssueResponse } from "types";
type Props = {
value: any;
onChange: (value: any) => void;
disabled?: boolean;
};
export const BlockerSelect: React.FC<Props> = (props) => {
const { value, onChange, disabled = false } = props;
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
const router = useRouter();
const { issueId } = router.query;
const { setToastAlert } = useToast();
const onSubmit = async (data: ISearchIssueResponse[]) => {
if (data.length === 0) {
setToastAlert({
type: "error",
title: "Error!",
message: "Please select at least one issue.",
});
return;
}
const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({
blocker_issue_detail: {
id: i.id,
name: i.name,
sequence_id: i.sequence_id,
project_detail: {
id: i.project_id,
identifier: i.project__identifier,
name: i.project__name,
},
},
}));
onChange([...(value || []), ...selectedIssues]);
setIsBlockerModalOpen(false);
};
return (
<>
<ExistingIssuesListModal
isOpen={isBlockerModalOpen}
handleClose={() => setIsBlockerModalOpen(false)}
searchParams={{ blocker_blocked_by: true, issue_id: issueId!.toString() }}
handleOnSubmit={onSubmit}
workspaceLevelToggle
/>
<button
type="button"
disabled={disabled}
onClick={() => setIsBlockerModalOpen(true)}
className={
"relative w-full px-2.5 py-0.5 text-base flex justify-between items-center gap-0.5 text-custom-text-100"
}
>
<span className="text-custom-text-200">Select issue</span>
<ChevronDown className="w-4 h-4 text-custom-text-200" />
</button>
</>
);
};
+2 -2
View File
@@ -2,7 +2,7 @@
import React, { useState } from "react";
// icons
import { ChevronDownIcon, PlayIcon } from "lucide-react";
import { ChevronDown, PlayIcon } from "lucide-react";
// hooks
import useEstimateOption from "hooks/use-estimate-option";
@@ -76,7 +76,7 @@ export const EstimateSelect: React.FC<Props> = (props) => {
) : (
"No estimate"
)}
<ChevronDownIcon className="w-5 h-5" />
<ChevronDown className="w-5 h-5" />
</button>
</>
);
+76
View File
@@ -0,0 +1,76 @@
// react
import React, { useState } from "react";
// next
import { useRouter } from "next/router";
// swr
import useSWR from "swr";
// services
import issuesService from "services/issues.service";
// fetch key
import { ISSUE_DETAILS } from "constants/fetch-keys";
// components
import { ParentIssuesListModal } from "components/issues";
// types
import { ISearchIssueResponse } from "types";
type Props = {
value: string | null;
onChange: (value: any) => void;
disabled?: boolean;
};
export const ParentSelect: React.FC<Props> = (props) => {
const { value, onChange, disabled = false } = props;
const [isParentModalOpen, setIsParentModalOpen] = useState(false);
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null,
workspaceSlug && projectId && issueId
? () =>
issuesService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString())
: null
);
return (
<>
<ParentIssuesListModal
isOpen={isParentModalOpen}
handleClose={() => setIsParentModalOpen(false)}
onChange={(issue) => {
onChange(issue.id);
setSelectedParentIssue(issue);
}}
issueId={issueId as string}
projectId={projectId as string}
/>
<button
type="button"
disabled={disabled}
onClick={() => setIsParentModalOpen(true)}
className={
"relative w-full px-2.5 py-0.5 text-base flex justify-between items-center gap-0.5 text-custom-text-100"
}
>
{selectedParentIssue && issueDetails?.parent ? (
`${selectedParentIssue.project__identifier}-${selectedParentIssue.sequence_id}`
) : !selectedParentIssue && issueDetails?.parent ? (
`${issueDetails.parent_detail?.project_detail.identifier}-${issueDetails.parent_detail?.sequence_id}`
) : (
<span className="text-custom-text-200">Select issue</span>
)}
</button>
</>
);
};
+7 -5
View File
@@ -2,21 +2,23 @@
import React, { useState } from "react";
// icons
import { ChevronDownIcon } from "lucide-react";
import { ChevronDown } from "lucide-react";
// constants
import { PRIORITIES } from "constants/project";
// components
import { getPriorityIcon } from "components/icons";
import { PriorityIcon } from "components/icons";
import { WebViewModal } from "./web-view-modal";
// helpers
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { TIssuePriorities } from "types";
type Props = {
value: any;
onChange: (value: any) => void;
onChange: (value: TIssuePriorities) => void;
disabled?: boolean;
};
@@ -59,7 +61,7 @@ export const PrioritySelect: React.FC<Props> = (props) => {
: "border-custom-border-200 text-custom-text-200"
}`}
>
{getPriorityIcon(priority, "text-sm")}
<PriorityIcon priority={priority} className="text-sm" />
</span>
),
})) || []
@@ -76,7 +78,7 @@ export const PrioritySelect: React.FC<Props> = (props) => {
}
>
{value ? capitalizeFirstLetter(value) : "None"}
<ChevronDownIcon className="w-5 h-5" />
<ChevronDown className="w-5 h-5" />
</button>
</>
);
+4 -4
View File
@@ -8,7 +8,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { ChevronDown } from "lucide-react";
// services
import stateService from "services/state.service";
@@ -17,7 +17,7 @@ import stateService from "services/state.service";
import { STATES_LIST } from "constants/fetch-keys";
// components
import { getStateGroupIcon } from "components/icons";
import { StateGroupIcon } from "components/icons";
import { WebViewModal } from "./web-view-modal";
// helpers
@@ -62,7 +62,7 @@ export const StateSelect: React.FC<Props> = (props) => {
label: state.name,
value: state.id,
checked: state.id === selectedState?.id,
icon: getStateGroupIcon(state.group, "16", "16", state.color),
icon: <StateGroupIcon stateGroup={state.group} color={state.color} />,
onClick: () => {
setIsOpen(false);
if (disabled) return;
@@ -82,7 +82,7 @@ export const StateSelect: React.FC<Props> = (props) => {
}
>
{selectedState?.name || "Select a state"}
<ChevronDownIcon className="w-5 h-5" />
<ChevronDown className="w-5 h-5" />
</button>
</>
);
+2 -2
View File
@@ -8,7 +8,7 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
// icons
import { XMarkIcon } from "@heroicons/react/24/outline";
import { X } from "lucide-react";
// services
import issuesService from "services/issues.service";
@@ -98,7 +98,7 @@ export const SubIssueList: React.FC<Props> = (props) => {
<p className="text-sm font-normal">{subIssue.name}</p>
</div>
<button type="button" onClick={() => handleSubIssueRemove(subIssue)}>
<XMarkIcon className="w-5 h-5 text-custom-text-200" />
<X className="w-[18px] h-[18px] text-custom-text-400" />
</button>
</div>
))}
+4 -4
View File
@@ -47,7 +47,7 @@ export const WebViewModal = (props: Props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-none rounded-tr-[4px] rounded-tl-[4px] bg-custom-background-100 p-6 text-left shadow-xl transition-all sm:mt-8 w-full">
<Dialog.Panel className="relative transform overflow-hidden rounded-none rounded-tr-[20px] rounded-tl-[20px] bg-custom-background-100 p-6 text-left shadow-xl transition-all sm:mt-8 w-full">
<div className="flex justify-between items-center w-full">
<Dialog.Title
as="h3"
@@ -63,7 +63,7 @@ export const WebViewModal = (props: Props) => {
<XMarkIcon className="w-6 h-6 text-custom-text-200" />
</button>
</div>
<div className="mt-6">{children}</div>
<div className="mt-6 max-h-60 overflow-auto">{children}</div>
</Dialog.Panel>
</Transition.Child>
</div>
@@ -84,9 +84,9 @@ type OptionsProps = {
};
const Options: React.FC<OptionsProps> = ({ options }) => (
<div className="space-y-6">
<div className="divide-y">
{options.map((option) => (
<div key={option.value} className="flex items-center justify-between gap-2 py-2">
<div key={option.value} className="flex items-center justify-between gap-2 py-[14px]">
<div className="flex items-center gap-x-2">
<input
type="checkbox"
@@ -1,9 +1,7 @@
// ui
import { PieGraph } from "components/ui";
// helpers
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { IUserStateDistribution } from "types";
import { IUserStateDistribution, TStateGroups } from "types";
// constants
import { STATE_GROUP_COLORS } from "constants/state";
@@ -23,7 +21,7 @@ export const IssuesPieChart: React.FC<Props> = ({ groupedIssues }) => (
id: cell.state_group,
label: cell.state_group,
value: cell.state_count,
color: STATE_GROUP_COLORS[cell.state_group.toLowerCase()],
color: STATE_GROUP_COLORS[cell.state_group.toLowerCase() as TStateGroups],
})) ?? []
}
height="320px"
+3 -1
View File
@@ -1,3 +1,5 @@
import { TIssuePriorities } from "types";
export const NETWORK_CHOICES: { key: 0 | 2; label: string; icon: string }[] = [
{
key: 0,
@@ -19,7 +21,7 @@ export const GROUP_CHOICES = {
cancelled: "Cancelled",
};
export const PRIORITIES = ["urgent", "high", "medium", "low", null];
export const PRIORITIES: TIssuePriorities[] = ["urgent", "high", "medium", "low", null];
export const MONTHS = [
"January",

Some files were not shown because too many files have changed in this diff Show More