forked from github/plane
fix: ui and bugs (#2289)
* fix: 24 character limit on first & last name in onboarding page * fix: no option: 'Add Issue' in archive issue page * fix: in archive issue directly sending to issue detail page * fix: issue type showing in archive issue * fix: custom menu overflowing * fix: changing subscriber in filters has no effect * style: border in quick-add * fix: on onboarding member role overflowing * fix: inconsistent icons in issue detail * style: spacing, borders and shadows in quick-add * fix: custom menu truncate
This commit is contained in:
parent
60a69e28e3
commit
ec91a0d2e5
@ -8,9 +8,27 @@ import useEstimateOption from "hooks/use-estimate-option";
|
||||
import issuesService from "services/issues.service";
|
||||
// icons
|
||||
import { Icon, Tooltip } from "components/ui";
|
||||
import { CopyPlus } from "lucide-react";
|
||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||
import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
|
||||
import {
|
||||
TagIcon,
|
||||
CopyPlus,
|
||||
Calendar,
|
||||
Link2Icon,
|
||||
RocketIcon,
|
||||
Users2Icon,
|
||||
ArchiveIcon,
|
||||
PaperclipIcon,
|
||||
ContrastIcon,
|
||||
TriangleIcon,
|
||||
LayoutGridIcon,
|
||||
SignalMediumIcon,
|
||||
MessageSquareIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
BlockedIcon,
|
||||
BlockerIcon,
|
||||
RelatedIcon,
|
||||
StackedLayersHorizontalIcon,
|
||||
} from "components/icons";
|
||||
// helpers
|
||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||
@ -38,7 +56,7 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||
{activity.issue_detail
|
||||
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
|
||||
: "Issue"}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
@ -131,14 +149,14 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="group" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <Users2Icon size={12} color="#6b7280" 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-2xl" aria-hidden="true" />,
|
||||
icon: <ArchiveIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
attachment: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -153,7 +171,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
attachment
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
@ -177,7 +195,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="attach_file" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <PaperclipIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
blocking: {
|
||||
message: (activity) => {
|
||||
@ -268,7 +286,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.new_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
@ -283,7 +301,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.new_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
@ -298,12 +316,12 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.old_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="contrast" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <ContrastIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
description: {
|
||||
message: (activity, showIssue) => (
|
||||
@ -318,7 +336,7 @@ const activityDetails: {
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="chat" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
estimate_point: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -349,14 +367,14 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="change_history" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <TriangleIcon size={12} color="#6b7280" 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-2xl" aria-hidden="true" />,
|
||||
icon: <StackedLayersHorizontalIcon width={12} height={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
labels: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -393,7 +411,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="sell" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <TagIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
link: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -408,7 +426,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
@ -430,7 +448,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
@ -452,7 +470,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
@ -464,7 +482,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="link" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <Link2Icon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
modules: {
|
||||
message: (activity, showIssue, workspaceSlug) => {
|
||||
@ -479,7 +497,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.new_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
@ -494,7 +512,7 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.new_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
@ -509,12 +527,12 @@ const activityDetails: {
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.old_value}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
<RocketIcon size={12} color="#6b7280" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="dataset" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <Icon iconName="dataset" className="!text-xs !text-[#6b7280]" aria-hidden="true" />,
|
||||
},
|
||||
name: {
|
||||
message: (activity, showIssue) => (
|
||||
@ -529,7 +547,7 @@ const activityDetails: {
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="chat" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <MessageSquareIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
parent: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -562,7 +580,13 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="supervised_user_circle" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: (
|
||||
<Icon
|
||||
iconName="supervised_user_circle"
|
||||
className="!text-xs !text-[#6b7280]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
),
|
||||
},
|
||||
priority: {
|
||||
message: (activity, showIssue) => (
|
||||
@ -580,7 +604,7 @@ const activityDetails: {
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="signal_cellular_alt" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <SignalMediumIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
start_date: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -614,7 +638,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="calendar_today" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
state: {
|
||||
message: (activity, showIssue) => (
|
||||
@ -630,7 +654,7 @@ const activityDetails: {
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Squares2X2Icon className="h-6 w-6 text-custom-sidebar-200" aria-hidden="true" />,
|
||||
icon: <LayoutGridIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
target_date: {
|
||||
message: (activity, showIssue) => {
|
||||
@ -664,7 +688,7 @@ const activityDetails: {
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="calendar_today" className="!text-2xl" aria-hidden="true" />,
|
||||
icon: <Calendar size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -200,7 +200,9 @@ export const FiltersList: React.FC<Props> = ({
|
||||
className="cursor-pointer"
|
||||
onClick={() =>
|
||||
setFilters({
|
||||
assignees: filters.assignees?.filter((p: any) => p !== memberId),
|
||||
subscriber: filters.subscriber?.filter(
|
||||
(p: any) => p !== memberId
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
|
@ -277,33 +277,35 @@ export const IssuesFilterView: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-custom-text-200">Issue type</h4>
|
||||
<div className="w-28">
|
||||
<CustomMenu
|
||||
label={
|
||||
FILTER_ISSUE_OPTIONS.find(
|
||||
(option) => option.key === displayFilters.type
|
||||
)?.name ?? "Select"
|
||||
}
|
||||
className="!w-full"
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
{FILTER_ISSUE_OPTIONS.map((option) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={option.key}
|
||||
onClick={() =>
|
||||
setDisplayFilters({
|
||||
type: option.key,
|
||||
})
|
||||
}
|
||||
>
|
||||
{option.name}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
{!isArchivedIssues && (
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-custom-text-200">Issue type</h4>
|
||||
<div className="w-28">
|
||||
<CustomMenu
|
||||
label={
|
||||
FILTER_ISSUE_OPTIONS.find(
|
||||
(option) => option.key === displayFilters.type
|
||||
)?.name ?? "Select"
|
||||
}
|
||||
className="!w-full"
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
{FILTER_ISSUE_OPTIONS.map((option) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={option.key}
|
||||
onClick={() =>
|
||||
setDisplayFilters({
|
||||
type: option.key,
|
||||
})
|
||||
}
|
||||
>
|
||||
{option.name}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{displayFilters.layout !== "calendar" &&
|
||||
displayFilters.layout !== "spreadsheet" && (
|
||||
|
@ -48,7 +48,7 @@ const InlineInput = () => {
|
||||
export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => (
|
||||
<>
|
||||
<InlineCreateIssueFormWrapper
|
||||
className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow-custom-shadow-sm"
|
||||
className="flex flex-col border-[0.5px] border-custom-border-100 justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow-custom-shadow-sm"
|
||||
{...props}
|
||||
>
|
||||
<InlineInput />
|
||||
|
@ -67,7 +67,7 @@ const InlineInput = () => {
|
||||
{...register("name", {
|
||||
required: "Issue title is required.",
|
||||
})}
|
||||
className="w-full pr-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
className="w-full pr-2 py-2.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -90,7 +90,7 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = (props) => {
|
||||
>
|
||||
<InlineCreateIssueFormWrapper
|
||||
{...props}
|
||||
className="flex w-full p-1 px-1.5 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm transition-opacity"
|
||||
className="flex w-full px-1.5 border-[0.5px] border-custom-border-100 rounded z-50 items-center gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm transition-opacity"
|
||||
>
|
||||
<InlineInput />
|
||||
</InlineCreateIssueFormWrapper>
|
||||
|
@ -48,7 +48,7 @@ const InlineInput = () => {
|
||||
export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => (
|
||||
<>
|
||||
<InlineCreateIssueFormWrapper
|
||||
className="flex py-3 px-4 mr-2.5 items-center rounded gap-x-2 border bg-custom-background-100 shadow-custom-shadow-sm"
|
||||
className="flex py-3 px-4 border-[0.5px] border-custom-border-100 mr-2.5 items-center rounded gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm"
|
||||
{...props}
|
||||
>
|
||||
<InlineInput />
|
||||
|
@ -81,7 +81,9 @@ export const IssuesView: React.FC<Props> = ({
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||
const isDraftIssues = router.asPath.includes("draft-issues");
|
||||
|
||||
const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues";
|
||||
const isArchivedIssues = router.pathname?.split("/")?.[4] === "archived-issues";
|
||||
|
||||
const { user } = useUserAuth();
|
||||
|
||||
@ -625,6 +627,7 @@ export const IssuesView: React.FC<Props> = ({
|
||||
params,
|
||||
properties,
|
||||
}}
|
||||
disableAddIssueOption={isArchivedIssues}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ const InlineInput = () => {
|
||||
{...register("name", {
|
||||
required: "Issue title is required.",
|
||||
})}
|
||||
className="w-full px-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
className="w-full px-2 py-3 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -48,7 +48,7 @@ const InlineInput = () => {
|
||||
export const ListInlineCreateIssueForm: React.FC<Props> = (props) => (
|
||||
<>
|
||||
<InlineCreateIssueFormWrapper
|
||||
className="flex py-3 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
|
||||
className="flex border-[0.5px] border-t-0 border-custom-border-100 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
|
||||
{...props}
|
||||
>
|
||||
<InlineInput />
|
||||
|
@ -328,7 +328,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
</ContextMenu>
|
||||
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"
|
||||
className="flex items-center justify-between px-4 py-2.5 gap-10 border-b-[0.5px] border-custom-border-100 bg-custom-background-100 last:border-b-0"
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setContextMenu(true);
|
||||
@ -352,6 +352,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
type="button"
|
||||
className="truncate text-[0.825rem] text-custom-text-100"
|
||||
onClick={() => {
|
||||
if (isArchivedIssues) return router.push(issuePath);
|
||||
if (!isDraftIssues) openPeekOverview(issue);
|
||||
if (isDraftIssues && handleDraftIssueSelect) handleDraftIssueSelect(issue);
|
||||
}}
|
||||
|
@ -239,7 +239,7 @@ export const SingleList: React.FC<Props> = (props) => {
|
||||
!disableAddIssueOption && (
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
|
||||
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
|
||||
addIssueToGroup();
|
||||
@ -336,7 +336,8 @@ export const SingleList: React.FC<Props> = (props) => {
|
||||
/>
|
||||
|
||||
{!disableAddIssueOption && !isCreateIssueFormOpen && (
|
||||
<div className="w-full bg-custom-background-100 px-6 py-3">
|
||||
// TODO: add border here
|
||||
<div className="w-full bg-custom-background-100 px-6 py-3 border-b border-custom-border-100">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
@ -54,7 +54,7 @@ export const AssigneeColumn: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.assignee && (
|
||||
<MembersSelect
|
||||
value={issue.assignees}
|
||||
|
@ -23,7 +23,7 @@ export const CreatedOnColumn: React.FC<Props> = ({
|
||||
isNotAllowed,
|
||||
}) => (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.created_on && (
|
||||
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
|
||||
{renderLongDetailDateFormat(issue.created_at)}
|
||||
|
@ -23,7 +23,7 @@ export const DueDateColumn: React.FC<Props> = ({
|
||||
isNotAllowed,
|
||||
}) => (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.due_date && (
|
||||
<ViewDueDateSelect
|
||||
issue={issue}
|
||||
|
@ -23,7 +23,7 @@ export const EstimateColumn: React.FC<Props> = ({
|
||||
isNotAllowed,
|
||||
}) => (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.estimate && (
|
||||
<ViewEstimateSelect
|
||||
issue={issue}
|
||||
|
@ -82,7 +82,7 @@ export const IssueColumn: React.FC<Props> = ({
|
||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||
|
||||
return (
|
||||
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-200 ">
|
||||
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-100">
|
||||
<div
|
||||
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center"
|
||||
style={issue.parent ? { paddingLeft } : {}}
|
||||
|
@ -28,7 +28,7 @@ export const LabelColumn: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.labels && (
|
||||
<LabelSelect
|
||||
value={issue.labels}
|
||||
|
@ -48,7 +48,7 @@ export const PriorityColumn: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.priority && (
|
||||
<PrioritySelect
|
||||
value={issue.priority}
|
||||
|
@ -287,7 +287,7 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="border-t border-custom-border-100">
|
||||
<div className="mb-3 z-50 sticky bottom-0 left-0">
|
||||
<ListInlineCreateIssueForm
|
||||
isOpen={isInlineCreateIssueFormOpen}
|
||||
@ -303,11 +303,11 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
? !disableUserActions &&
|
||||
!isInlineCreateIssueFormOpen && (
|
||||
<button
|
||||
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
|
||||
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] w-full"
|
||||
onClick={() => setIsInlineCreateIssueFormOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
New Issue
|
||||
</button>
|
||||
)
|
||||
: !disableUserActions &&
|
||||
@ -316,11 +316,11 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
className="sticky left-0 z-10"
|
||||
customButton={
|
||||
<button
|
||||
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
|
||||
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
|
||||
type="button"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
New Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
|
@ -23,7 +23,7 @@ export const StartDateColumn: React.FC<Props> = ({
|
||||
isNotAllowed,
|
||||
}) => (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.due_date && (
|
||||
<ViewStartDateSelect
|
||||
issue={issue}
|
||||
|
@ -70,7 +70,7 @@ export const StateColumn: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.state && (
|
||||
<StateSelect
|
||||
value={issue.state_detail}
|
||||
|
@ -23,7 +23,7 @@ export const UpdatedOnColumn: React.FC<Props> = ({
|
||||
isNotAllowed,
|
||||
}) => (
|
||||
<div className="flex items-center text-sm h-11 w-full bg-custom-background-100">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-200">
|
||||
<span className="flex items-center px-4 py-2.5 h-full w-full flex-shrink-0 border-r border-b border-custom-border-100">
|
||||
{properties.updated_on && (
|
||||
<div className="flex items-center text-xs cursor-default text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
|
||||
{renderLongDetailDateFormat(issue.updated_at)}
|
||||
|
@ -347,7 +347,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCreateIssueFormOpen(true)}
|
||||
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md"
|
||||
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 pl-[1.875rem] py-1 rounded-md"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
|
||||
|
@ -1,15 +1,27 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
// headless ui
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// react-hook-form
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import {
|
||||
Control,
|
||||
Controller,
|
||||
FieldArrayWithId,
|
||||
UseFieldArrayRemove,
|
||||
useFieldArray,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui";
|
||||
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
||||
// hooks
|
||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
||||
// icons
|
||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import { PlusIcon, XMarkIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { ICurrentUserResponse, IWorkspace, TOnboardingSteps } from "types";
|
||||
// constants
|
||||
@ -31,12 +43,136 @@ type FormValues = {
|
||||
emails: EmailRole[];
|
||||
};
|
||||
|
||||
export const InviteMembers: React.FC<Props> = ({
|
||||
finishOnboarding,
|
||||
stepChange,
|
||||
user,
|
||||
workspace,
|
||||
}) => {
|
||||
type InviteMemberFormProps = {
|
||||
index: number;
|
||||
remove: UseFieldArrayRemove;
|
||||
control: Control<FormValues, any>;
|
||||
field: FieldArrayWithId<FormValues, "emails", "id">;
|
||||
fields: FieldArrayWithId<FormValues, "emails", "id">[];
|
||||
errors: any;
|
||||
};
|
||||
|
||||
const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
const { control, index, fields, remove, errors } = props;
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
useDynamicDropdownPosition(
|
||||
isDropdownOpen,
|
||||
() => setIsDropdownOpen(false),
|
||||
buttonRef,
|
||||
dropdownRef
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="group relative grid grid-cols-11 gap-4">
|
||||
<div className="col-span-7">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`emails.${index}.email`}
|
||||
rules={{
|
||||
required: "Email ID is required",
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: "Invalid Email ID",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Input {...field} className="text-xs sm:text-sm" placeholder="Enter their email..." />
|
||||
{errors.emails?.[index]?.email && (
|
||||
<span className="text-red-500 text-xs">
|
||||
{errors.emails?.[index]?.email?.message}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`emails.${index}.role`}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange(val);
|
||||
setIsDropdownOpen(false);
|
||||
}}
|
||||
className="flex-shrink-0 text-left w-full"
|
||||
>
|
||||
<Listbox.Button
|
||||
type="button"
|
||||
ref={buttonRef}
|
||||
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
||||
className="flex items-center px-2.5 py-2 text-xs justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none"
|
||||
>
|
||||
<span className="text-xs sm:text-sm">{ROLE[value]}</span>
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={isDropdownOpen}
|
||||
as={React.Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Listbox.Options
|
||||
ref={dropdownRef}
|
||||
className="fixed w-36 z-10 border border-custom-border-300 mt-1 overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none max-h-48"
|
||||
>
|
||||
<div className="space-y-1 p-2">
|
||||
{Object.entries(ROLE).map(([key, value]) => (
|
||||
<Listbox.Option
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active || selected ? "bg-custom-background-80" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">{value}</div>
|
||||
{selected && <CheckIcon className="h-4 w-4 flex-shrink-0" />}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{fields.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="hidden group-hover:grid self-center place-items-center rounded -ml-3"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<XMarkIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const InviteMembers: React.FC<Props> = (props) => {
|
||||
const { finishOnboarding, stepChange, user, workspace } = props;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const {
|
||||
@ -109,66 +245,15 @@ export const InviteMembers: React.FC<Props> = ({
|
||||
</div>
|
||||
<div className="space-y-3 sm:space-y-4 mb-3 h-full overflow-y-auto">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="group relative grid grid-cols-11 gap-4">
|
||||
<div className="col-span-7">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`emails.${index}.email`}
|
||||
rules={{
|
||||
required: "Email ID is required",
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: "Invalid Email ID",
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Input
|
||||
{...field}
|
||||
className="text-xs sm:text-sm"
|
||||
placeholder="Enter their email..."
|
||||
/>
|
||||
{errors.emails?.[index]?.email && (
|
||||
<span className="text-red-500 text-xs">
|
||||
{errors.emails?.[index]?.email?.message}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`emails.${index}.role`}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
label={<span className="text-xs sm:text-sm">{ROLE[value]}</span>}
|
||||
onChange={onChange}
|
||||
width="w-full"
|
||||
input
|
||||
>
|
||||
{Object.entries(ROLE).map(([key, value]) => (
|
||||
<CustomSelect.Option key={key} value={parseInt(key)}>
|
||||
{value}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{fields.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="hidden group-hover:grid self-center place-items-center rounded -ml-3"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<XMarkIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<InviteMemberForm
|
||||
control={control}
|
||||
errors={errors}
|
||||
field={field}
|
||||
fields={fields}
|
||||
index={index}
|
||||
remove={remove}
|
||||
key={field.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
|
@ -121,6 +121,10 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
register={register}
|
||||
validations={{
|
||||
required: "First name is required",
|
||||
maxLength: {
|
||||
value: 24,
|
||||
message: "First name cannot exceed the limit of 24 characters",
|
||||
},
|
||||
}}
|
||||
error={errors.first_name}
|
||||
/>
|
||||
@ -135,6 +139,10 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
placeholder="Enter your last name..."
|
||||
validations={{
|
||||
required: "Last name is required",
|
||||
maxLength: {
|
||||
value: 24,
|
||||
message: "Last name cannot exceed the limit of 24 characters",
|
||||
},
|
||||
}}
|
||||
error={errors.last_name}
|
||||
/>
|
||||
|
@ -239,9 +239,11 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
||||
<div className="w-28">
|
||||
<CustomMenu
|
||||
label={
|
||||
FILTER_ISSUE_OPTIONS.find(
|
||||
(option) => option.key === displayFilters?.type
|
||||
)?.name ?? "Select"
|
||||
<span className="truncate">
|
||||
{FILTER_ISSUE_OPTIONS.find(
|
||||
(option) => option.key === displayFilters?.type
|
||||
)?.name ?? "Select"}
|
||||
</span>
|
||||
}
|
||||
className="!w-full"
|
||||
buttonClassName="w-full"
|
||||
|
@ -14,6 +14,7 @@ const paramsToKey = (params: any) => {
|
||||
sub_issue,
|
||||
start_target_date,
|
||||
project,
|
||||
subscriber,
|
||||
} = params;
|
||||
|
||||
let projectKey = project ? project.split(",") : [];
|
||||
@ -23,6 +24,7 @@ const paramsToKey = (params: any) => {
|
||||
let assigneesKey = assignees ? assignees.split(",") : [];
|
||||
let createdByKey = created_by ? created_by.split(",") : [];
|
||||
let labelsKey = labels ? labels.split(",") : [];
|
||||
let subscriberKey = subscriber ? subscriber.split(",") : [];
|
||||
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
|
||||
const startDateKey = start_date ?? "";
|
||||
const targetDateKey = target_date ?? "";
|
||||
@ -38,8 +40,9 @@ const paramsToKey = (params: any) => {
|
||||
assigneesKey = assigneesKey.sort().join("_");
|
||||
createdByKey = createdByKey.sort().join("_");
|
||||
labelsKey = labelsKey.sort().join("_");
|
||||
subscriberKey = subscriberKey.sort().join("_");
|
||||
|
||||
return `${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}`;
|
||||
return `${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}_${subscriberKey}`;
|
||||
};
|
||||
|
||||
const inboxParamsToKey = (params: any) => {
|
||||
|
@ -47,7 +47,7 @@ const useIssuesView = () => {
|
||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
||||
state: filters?.state ? filters?.state.join(",") : undefined,
|
||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
||||
type: displayFilters?.type ? displayFilters?.type : undefined,
|
||||
type: !isArchivedIssues ? (displayFilters?.type ? displayFilters?.type : undefined) : undefined,
|
||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
||||
|
Loading…
Reference in New Issue
Block a user