forked from github/plane
Merge branch 'develop' of gurusainath:makeplane/plane into fix/kanban-sorting
This commit is contained in:
commit
a8c5a4155b
2
.github/workflows/Update_Docker_Images.yml
vendored
2
.github/workflows/Update_Docker_Images.yml
vendored
@ -2,7 +2,7 @@ name: Update Docker Images for Plane on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
types: [released, prereleased]
|
||||
|
||||
jobs:
|
||||
build_push_backend:
|
||||
|
@ -178,7 +178,7 @@ class IssueViewSet(BaseViewSet):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
@ -331,7 +331,7 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
try:
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
@ -1068,7 +1068,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
show_sub_issues = request.GET.get("show_sub_issues", "true")
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
@ -2078,7 +2078,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||
|
||||
order_by_param = request.GET.get("order_by", "-created_at")
|
||||
|
@ -1072,7 +1072,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
.order_by("state_group")
|
||||
)
|
||||
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
|
||||
priority_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
|
@ -38,6 +38,7 @@ class Issue(ProjectBaseModel):
|
||||
("high", "High"),
|
||||
("medium", "Medium"),
|
||||
("low", "Low"),
|
||||
("none", "None")
|
||||
)
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
@ -64,8 +65,7 @@ class Issue(ProjectBaseModel):
|
||||
max_length=30,
|
||||
choices=PRIORITY_CHOICES,
|
||||
verbose_name="Issue Priority",
|
||||
null=True,
|
||||
blank=True,
|
||||
default="none",
|
||||
)
|
||||
start_date = models.DateField(null=True, blank=True)
|
||||
target_date = models.DateField(null=True, blank=True)
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.dateparse import parse_datetime
|
||||
|
||||
|
||||
def filter_state(params, filter, method):
|
||||
if method == "GET":
|
||||
states = params.get("state").split(",")
|
||||
@ -23,7 +24,6 @@ def filter_state_group(params, filter, method):
|
||||
return filter
|
||||
|
||||
|
||||
|
||||
def filter_estimate_point(params, filter, method):
|
||||
if method == "GET":
|
||||
estimate_points = params.get("estimate_point").split(",")
|
||||
@ -39,25 +39,10 @@ def filter_priority(params, filter, method):
|
||||
if method == "GET":
|
||||
priorities = params.get("priority").split(",")
|
||||
if len(priorities) and "" not in priorities:
|
||||
if len(priorities) == 1 and "null" in priorities:
|
||||
filter["priority__isnull"] = True
|
||||
elif len(priorities) > 1 and "null" in priorities:
|
||||
filter["priority__isnull"] = True
|
||||
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||
else:
|
||||
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||
|
||||
filter["priority__in"] = priorities
|
||||
else:
|
||||
if params.get("priority", None) and len(params.get("priority")):
|
||||
priorities = params.get("priority")
|
||||
if len(priorities) == 1 and "null" in priorities:
|
||||
filter["priority__isnull"] = True
|
||||
elif len(priorities) > 1 and "null" in priorities:
|
||||
filter["priority__isnull"] = True
|
||||
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||
else:
|
||||
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||
|
||||
filter["priority__in"] = params.get("priority")
|
||||
return filter
|
||||
|
||||
|
||||
@ -229,7 +214,6 @@ def filter_issue_state_type(params, filter, method):
|
||||
return filter
|
||||
|
||||
|
||||
|
||||
def filter_project(params, filter, method):
|
||||
if method == "GET":
|
||||
projects = params.get("project").split(",")
|
||||
|
@ -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:
|
||||
|
@ -90,8 +90,6 @@ services:
|
||||
DOCKER_BUILDKIT: 1
|
||||
restart: always
|
||||
command: ./bin/takeoff
|
||||
ports:
|
||||
- 8000:8000
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
@ -8,7 +8,6 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"start": "turbo run start",
|
||||
|
@ -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">
|
||||
|
@ -56,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, () => ({
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
@ -84,8 +84,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);
|
||||
|
@ -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,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>
|
||||
);
|
@ -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";
|
||||
|
@ -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>
|
||||
<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>
|
||||
);
|
||||
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>;
|
||||
}
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
@ -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
web/components/icons/state/backlog.tsx
Normal file
24
web/components/icons/state/backlog.tsx
Normal 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
web/components/icons/state/cancelled.tsx
Normal file
34
web/components/icons/state/cancelled.tsx
Normal 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
web/components/icons/state/completed.tsx
Normal file
27
web/components/icons/state/completed.tsx
Normal 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
web/components/icons/state/index.ts
Normal file
6
web/components/icons/state/index.ts
Normal 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
web/components/icons/state/started.tsx
Normal file
25
web/components/icons/state/started.tsx
Normal 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>
|
||||
);
|
74
web/components/icons/state/state-group-icon.tsx
Normal file
74
web/components/icons/state/state-group-icon.tsx
Normal file
@ -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
web/components/icons/state/unstarted.tsx
Normal file
24
web/components/icons/state/unstarted.tsx
Normal 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>
|
||||
);
|
@ -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: {
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
@ -83,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: {
|
||||
@ -104,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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -9,13 +9,16 @@ import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
// ui
|
||||
import { CustomSelect, Input, PrimaryButton } from "components/ui";
|
||||
import { CustomSearchSelect, CustomSelect, Input, PrimaryButton } from "components/ui";
|
||||
// types
|
||||
import { ICurrentUserResponse, IUser } from "types";
|
||||
// fetch-keys
|
||||
import { CURRENT_USER } from "constants/fetch-keys";
|
||||
// helpers
|
||||
import { getUserTimeZoneFromWindow } from "helpers/date-time.helper";
|
||||
// constants
|
||||
import { USER_ROLES } from "constants/workspace";
|
||||
import { TIME_ZONES } from "constants/timezones";
|
||||
|
||||
const defaultValues: Partial<IUser> = {
|
||||
first_name: "",
|
||||
@ -27,6 +30,12 @@ type Props = {
|
||||
user?: IUser;
|
||||
};
|
||||
|
||||
const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
|
||||
value: timeZone.value,
|
||||
query: timeZone.label + " " + timeZone.value,
|
||||
content: timeZone.label,
|
||||
}));
|
||||
|
||||
export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -84,6 +93,7 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
role: user.role,
|
||||
user_timezone: getUserTimeZoneFromWindow(),
|
||||
});
|
||||
}
|
||||
}, [user, reset]);
|
||||
@ -162,6 +172,34 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
{errors.role && <span className="text-sm text-red-500">{errors.role.message}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 text-sm">
|
||||
<span>What time zone are you in? </span>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
name="user_timezone"
|
||||
control={control}
|
||||
rules={{ required: "This field is required" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSearchSelect
|
||||
value={value}
|
||||
label={
|
||||
value
|
||||
? TIME_ZONES.find((t) => t.value === value)?.label ?? value
|
||||
: "Select a timezone"
|
||||
}
|
||||
options={timeZoneOptions}
|
||||
onChange={onChange}
|
||||
verticalPosition="top"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors?.user_timezone && (
|
||||
<span className="text-sm text-red-500">{errors.user_timezone.message}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PrimaryButton type="submit" size="md" disabled={!isValid} loading={isSubmitting}>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -56,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, () => ({
|
||||
|
@ -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";
|
||||
@ -99,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: {
|
||||
@ -118,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: {
|
||||
|
419
web/components/web-view/activity-message.tsx
Normal file
419
web/components/web-view/activity-message.tsx
Normal file
@ -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() ?? ""
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -15,3 +15,4 @@ export * from "./add-comment";
|
||||
export * from "./select-parent";
|
||||
export * from "./select-blocker";
|
||||
export * from "./select-blocked";
|
||||
export * from "./activity-message";
|
||||
|
@ -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";
|
||||
@ -183,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>
|
||||
</button>
|
||||
)}{" "}
|
||||
{message}{" "}
|
||||
<span className="whitespace-nowrap">
|
||||
|
@ -109,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 })}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -8,15 +8,17 @@ import { ChevronDown } from "lucide-react";
|
||||
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>
|
||||
),
|
||||
})) || []
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { TStateGroups } from "types";
|
||||
|
||||
export const STATE_GROUP_COLORS: {
|
||||
[key: string]: string;
|
||||
[key in TStateGroups]: string;
|
||||
} = {
|
||||
backlog: "#d9d9d9",
|
||||
unstarted: "#3f76ff",
|
||||
|
@ -3,7 +3,7 @@ import { BarDatum } from "@nivo/bar";
|
||||
// helpers
|
||||
import { capitalizeFirstLetter, generateRandomColor } from "helpers/string.helper";
|
||||
// types
|
||||
import { IAnalyticsData, IAnalyticsParams, IAnalyticsResponse } from "types";
|
||||
import { IAnalyticsData, IAnalyticsParams, IAnalyticsResponse, TStateGroups } from "types";
|
||||
// constants
|
||||
import { STATE_GROUP_COLORS } from "constants/state";
|
||||
import { MONTHS_LIST } from "constants/calendar";
|
||||
@ -72,7 +72,8 @@ export const generateBarColor = (
|
||||
if (params[type] === "state__name" || params[type] === "labels__name")
|
||||
color = analytics?.extras?.colors.find((c) => c.name === value)?.color;
|
||||
|
||||
if (params[type] === "state__group") color = STATE_GROUP_COLORS[value.toLowerCase()];
|
||||
if (params[type] === "state__group")
|
||||
color = STATE_GROUP_COLORS[value.toLowerCase() as TStateGroups];
|
||||
|
||||
if (params[type] === "priority") {
|
||||
const priority = value.toLowerCase();
|
||||
|
@ -403,3 +403,5 @@ export const findTotalDaysInRange = (
|
||||
|
||||
return diffInDays;
|
||||
};
|
||||
|
||||
export const getUserTimeZoneFromWindow = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
@ -15,10 +15,14 @@ import { Spinner } from "components/ui";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
fullScreen?: boolean;
|
||||
};
|
||||
|
||||
const getIfInWebview = (userAgent: NavigatorID["userAgent"]) => {
|
||||
if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true;
|
||||
const safari = /safari/.test(userAgent);
|
||||
|
||||
if (safari) return false;
|
||||
else if (/iphone|ipod|ipad/.test(userAgent) || userAgent.includes("wv")) return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
@ -27,7 +31,7 @@ const useMobileDetect = () => {
|
||||
return getIfInWebview(userAgent);
|
||||
};
|
||||
|
||||
const WebViewLayout: React.FC<Props> = ({ children }) => {
|
||||
const WebViewLayout: React.FC<Props> = ({ children, fullScreen = true }) => {
|
||||
const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser());
|
||||
|
||||
const isWebview = useMobileDetect();
|
||||
@ -44,7 +48,7 @@ const WebViewLayout: React.FC<Props> = ({ children }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen w-full bg-custom-background-100">
|
||||
<div className={fullScreen ? "h-screen w-full bg-custom-background-100" : ""}>
|
||||
{error || !isWebview ? (
|
||||
<div className="flex flex-col items-center justify-center gap-y-3 h-full text-center text-custom-text-200">
|
||||
<AlertCircle size={64} />
|
||||
|
@ -26,7 +26,7 @@ import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
|
||||
const defaultValues = {
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
name: "",
|
||||
description: "",
|
||||
description_html: "",
|
||||
|
@ -27,7 +27,7 @@ import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
|
||||
const defaultValues = {
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
assignees_list: [],
|
||||
description: "",
|
||||
description_html: "",
|
||||
|
@ -1,37 +1,20 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
// services
|
||||
import appinstallationsService from "services/app-installations.service";
|
||||
|
||||
import useToast from "hooks/use-toast";
|
||||
|
||||
// components
|
||||
import { Spinner } from "components/ui";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface IGithuPostInstallationProps {
|
||||
installation_id: string;
|
||||
setup_action: string;
|
||||
state: string;
|
||||
provider: string;
|
||||
code: string;
|
||||
}
|
||||
// services
|
||||
import appInstallationsService from "services/app-installations.service";
|
||||
// ui
|
||||
import { Spinner } from "components/ui";
|
||||
|
||||
// TODO:Change getServerSideProps to router.query
|
||||
const AppPostInstallation = ({
|
||||
installation_id,
|
||||
setup_action,
|
||||
state,
|
||||
provider,
|
||||
code,
|
||||
}: IGithuPostInstallationProps) => {
|
||||
const { setToastAlert } = useToast();
|
||||
const AppPostInstallation = () => {
|
||||
const router = useRouter();
|
||||
const { installation_id, setup_action, state, provider, code } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
if (provider === "github" && state && installation_id) {
|
||||
appinstallationsService
|
||||
.addInstallationApp(state, provider, { installation_id })
|
||||
appInstallationsService
|
||||
.addInstallationApp(state.toString(), provider, { installation_id })
|
||||
.then(() => {
|
||||
window.opener = null;
|
||||
window.open("", "_self");
|
||||
@ -41,10 +24,10 @@ const AppPostInstallation = ({
|
||||
console.log(err);
|
||||
});
|
||||
} else if (provider === "slack" && state && code) {
|
||||
appinstallationsService
|
||||
.getSlackAuthDetails(code)
|
||||
appInstallationsService
|
||||
.getSlackAuthDetails(code.toString())
|
||||
.then((res) => {
|
||||
const [workspaceSlug, projectId, integrationId] = state.split(",");
|
||||
const [workspaceSlug, projectId, integrationId] = state.toString().split(",");
|
||||
|
||||
if (!projectId) {
|
||||
const payload = {
|
||||
@ -53,8 +36,8 @@ const AppPostInstallation = ({
|
||||
},
|
||||
};
|
||||
|
||||
appinstallationsService
|
||||
.addInstallationApp(state, provider, payload)
|
||||
appInstallationsService
|
||||
.addInstallationApp(state.toString(), provider, payload)
|
||||
.then((r) => {
|
||||
window.opener = null;
|
||||
window.open("", "_self");
|
||||
@ -73,7 +56,7 @@ const AppPostInstallation = ({
|
||||
team_name: res.team.name,
|
||||
scopes: res.scope,
|
||||
};
|
||||
appinstallationsService
|
||||
appInstallationsService
|
||||
.addSlackChannel(workspaceSlug, projectId, integrationId, payload)
|
||||
.then((r) => {
|
||||
window.opener = null;
|
||||
@ -99,10 +82,4 @@ const AppPostInstallation = ({
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: context.query,
|
||||
};
|
||||
}
|
||||
|
||||
export default AppPostInstallation;
|
||||
|
99
web/pages/m/[workspaceSlug]/editor.tsx
Normal file
99
web/pages/m/[workspaceSlug]/editor.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// next
|
||||
import type { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// cookies
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
// react-hook-form
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
// layouts
|
||||
import WebViewLayout from "layouts/web-view-layout";
|
||||
|
||||
// components
|
||||
import { TipTapEditor } from "components/tiptap";
|
||||
import { PrimaryButton, Spinner } from "components/ui";
|
||||
|
||||
const Editor: NextPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, editable } = router.query;
|
||||
|
||||
const isEditable = editable === "true";
|
||||
|
||||
const { watch, setValue, control } = useForm({
|
||||
defaultValues: {
|
||||
data: "",
|
||||
data_html: "",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!router?.query?.["editable"]) return;
|
||||
setIsLoading(false);
|
||||
const data_html = Cookies.get("data_html");
|
||||
setValue("data_html", data_html ?? "");
|
||||
}, [isEditable, setValue, router]);
|
||||
|
||||
return (
|
||||
<WebViewLayout fullScreen={isLoading}>
|
||||
{isLoading ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Controller
|
||||
name="data_html"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<TipTapEditor
|
||||
borderOnFocus={false}
|
||||
value={
|
||||
!value ||
|
||||
value === "" ||
|
||||
(typeof value === "object" && Object.keys(value).length === 0)
|
||||
? watch("data_html")
|
||||
: value
|
||||
}
|
||||
editable={isEditable}
|
||||
noBorder={true}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
debouncedUpdatesEnabled={true}
|
||||
customClassName="min-h-[150px] shadow-sm"
|
||||
editorContentCustomClassNames="pb-9"
|
||||
onChange={(description: Object, description_html: string) => {
|
||||
onChange(description_html);
|
||||
setValue("data_html", description_html);
|
||||
setValue("data", JSON.stringify(description));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isEditable && (
|
||||
<PrimaryButton
|
||||
className="mt-4 w-[calc(100%-30px)] h-[45px] mx-[15px] text-[17px]"
|
||||
onClick={() => {
|
||||
console.log(
|
||||
"submitted",
|
||||
JSON.stringify({
|
||||
data_html: watch("data_html"),
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</PrimaryButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</WebViewLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor;
|
4
web/types/issues.d.ts
vendored
4
web/types/issues.d.ts
vendored
@ -102,7 +102,7 @@ export interface IIssue {
|
||||
name: string;
|
||||
parent: string | null;
|
||||
parent_detail: IIssueParent | null;
|
||||
priority: string | null;
|
||||
priority: TIssuePriorities;
|
||||
project: string;
|
||||
project_detail: IProjectLite;
|
||||
sequence_id: number;
|
||||
@ -294,3 +294,5 @@ export interface IIssueViewProps {
|
||||
properties: Properties;
|
||||
showEmptyGroups: boolean;
|
||||
}
|
||||
|
||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | null;
|
||||
|
Loading…
Reference in New Issue
Block a user