Refactoring Phase 1 (#199)
* style: added cta at the bottom of sidebar, added missing icons as well, showing dynamic workspace member count on workspace dropdown * refractor: running parallel request, made create/edit label function to async function * fix: sidebar dropdown content going below kanban items outside click detection in need help dropdown * refractor: making parallel api calls fix: create state input comes at bottom, create state input gets on focus automatically, form is getting submitted on enter click * refactoring file structure and signin page * style: changed text and added spinner for signing in loading * refractor: removed unused type * fix: my issue cta in profile page sending to 404 page * fix: added new s3 bucket url in next.config.js file increased image modal height * packaging UI components * eslint config * eslint fixes * refactoring changes * build fixes * minor fixes * adding todo comments for reference * refactor: cleared unused imports and re ordered imports * refactor: removed unused imports * fix: added workspace argument to useissues hook * refactor: removed api-routes file, unnecessary constants * refactor: created helpers folder, removed unnecessary constants * refactor: new context for issue view * refactoring issues page * build fixes * refactoring * refactor: create issue modal * refactor: module ui * fix: sub-issues mutation * fix: create more option in create issue modal * description form debounce issue * refactor: global component for assignees list * fix: link module interface * fix: priority icons and sub-issues count added * fix: cycle mutation in issue details page * fix: remove issue from cycle mutation * fix: create issue modal in home page * fix: removed unnecessary props * fix: updated create issue form status * fix: settings auth breaking * refactor: issue details page Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com> Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: venkatesh-soulpage <venkatesh.marreboyina@soulpageit.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
This commit is contained in:
parent
9134b0c543
commit
9075f9441c
322 changed files with 14149 additions and 21378 deletions
|
|
@ -4,13 +4,24 @@ import { useRouter } from "next/router";
|
|||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
import { useForm, Controller, UseFormWatch } from "react-hook-form";
|
||||
import { useForm, Controller, UseFormWatch, Control } from "react-hook-form";
|
||||
|
||||
import { TwitterPicker } from "react-color";
|
||||
// services
|
||||
import issuesServices from "lib/services/issues.service";
|
||||
import { Popover, Listbox, Transition } from "@headlessui/react";
|
||||
import {
|
||||
TagIcon,
|
||||
ChevronDownIcon,
|
||||
LinkIcon,
|
||||
CalendarDaysIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// hooks
|
||||
import useToast from "lib/hooks/useToast";
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import issuesServices from "services/issues.service";
|
||||
// components
|
||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||
import SelectState from "components/project/issues/issue-detail/issue-detail-sidebar/select-state";
|
||||
|
|
@ -21,26 +32,15 @@ import SelectAssignee from "components/project/issues/issue-detail/issue-detail-
|
|||
import SelectBlocker from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocker";
|
||||
import SelectBlocked from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocked";
|
||||
// headless ui
|
||||
import { Popover, Listbox, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Input, Button, Spinner } from "ui";
|
||||
import { Input, Button, Spinner } from "components/ui";
|
||||
// icons
|
||||
import {
|
||||
TagIcon,
|
||||
ChevronDownIcon,
|
||||
LinkIcon,
|
||||
CalendarDaysIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
import type { Control } from "react-hook-form";
|
||||
import type { ICycle, IIssue, IIssueLabels } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
// common
|
||||
import { copyTextToClipboard } from "constants/common";
|
||||
import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
control: Control<IIssue, any>;
|
||||
|
|
@ -64,7 +64,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
|
@ -110,11 +110,13 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
const handleCycleChange = (cycleDetail: ICycle) => {
|
||||
if (!workspaceSlug || !projectId || !issueDetail) return;
|
||||
|
||||
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
||||
|
||||
issuesServices.addIssueToCycle(workspaceSlug as string, projectId as string, cycleDetail.id, {
|
||||
issues: [issueDetail.id],
|
||||
});
|
||||
issuesServices
|
||||
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleDetail.id, {
|
||||
issues: [issueDetail.id],
|
||||
})
|
||||
.then((res) => {
|
||||
mutate(ISSUE_DETAILS(issueId as string));
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -124,7 +126,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
isOpen={deleteIssueModal}
|
||||
data={issueDetail}
|
||||
/>
|
||||
<div className="h-full w-full divide-y-2 divide-gray-100">
|
||||
<div className="w-full divide-y-2 divide-gray-100 sticky top-5">
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
<h4 className="text-sm font-medium">
|
||||
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
||||
|
|
@ -236,7 +238,6 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
<div className="py-1">
|
||||
<SelectCycle
|
||||
issueDetail={issueDetail}
|
||||
control={control}
|
||||
handleCycleChange={handleCycleChange}
|
||||
watch={watchIssue}
|
||||
/>
|
||||
|
|
@ -269,7 +270,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
<span
|
||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: singleLabel.colour ?? "green" }}
|
||||
></span>
|
||||
/>
|
||||
{singleLabel.name}
|
||||
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />
|
||||
</span>
|
||||
|
|
@ -318,7 +319,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
<span
|
||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: label.colour ?? "green" }}
|
||||
></span>
|
||||
/>
|
||||
{label.name}
|
||||
</Listbox.Option>
|
||||
))
|
||||
|
|
@ -370,7 +371,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||
style={{
|
||||
backgroundColor: watch("colour") ?? "green",
|
||||
}}
|
||||
></span>
|
||||
/>
|
||||
)}
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Popover.Button>
|
||||
|
|
|
|||
|
|
@ -7,20 +7,19 @@ import useSWR from "swr";
|
|||
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// services
|
||||
import workspaceService from "lib/services/workspace.service";
|
||||
// hooks
|
||||
import useUser from "lib/hooks/useUser";
|
||||
// headless ui
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Spinner } from "ui";
|
||||
// icons
|
||||
import { UserGroupIcon } from "@heroicons/react/24/outline";
|
||||
import workspaceService from "services/workspace.service";
|
||||
// hooks
|
||||
// headless ui
|
||||
// ui
|
||||
import { AssigneesList } from "components/ui/avatar";
|
||||
import { Spinner } from "components/ui";
|
||||
// icons
|
||||
import User from "public/user.png";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
import { classNames } from "constants/common";
|
||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -61,59 +60,13 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
|
|||
<div className="relative">
|
||||
<Listbox.Button className="flex w-full cursor-pointer items-center gap-1 text-xs">
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate text-left sm:block"
|
||||
)}
|
||||
className={`hidden truncate text-left sm:block ${
|
||||
value ? "" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<div className="flex cursor-pointer items-center gap-1 text-xs">
|
||||
{value && Array.isArray(value) ? (
|
||||
<>
|
||||
{value.length > 0 ? (
|
||||
value.map((assignee, index: number) => {
|
||||
const person = people?.find(
|
||||
(p) => p.member.id === assignee
|
||||
)?.member;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`relative z-[1] h-5 w-5 rounded-full ${
|
||||
index !== 0 ? "-ml-2.5" : ""
|
||||
}`}
|
||||
>
|
||||
{person && person.avatar && person.avatar !== "" ? (
|
||||
<div className="h-5 w-5 rounded-full border-2 border-white bg-white">
|
||||
<Image
|
||||
src={person.avatar}
|
||||
height="100%"
|
||||
width="100%"
|
||||
className="rounded-full"
|
||||
alt={person.first_name}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`grid h-5 w-5 place-items-center rounded-full border-2 border-white bg-gray-700 text-white`}
|
||||
>
|
||||
{person?.first_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="h-5 w-5 rounded-full border-2 border-white bg-white">
|
||||
<Image
|
||||
src={User}
|
||||
height="100%"
|
||||
width="100%"
|
||||
className="rounded-full"
|
||||
alt="No user"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<AssigneesList userIds={value} length={10} />
|
||||
) : null}
|
||||
</div>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -5,27 +5,26 @@ import { useRouter } from "next/router";
|
|||
|
||||
import useSWR from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { SubmitHandler, useForm, UseFormWatch } from "react-hook-form";
|
||||
// services
|
||||
import issuesService from "lib/services/issues.service";
|
||||
// constants
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
// hooks
|
||||
import useToast from "lib/hooks/useToast";
|
||||
// headless ui
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
// ui
|
||||
import { Button } from "ui";
|
||||
import { Button } from "components/ui";
|
||||
// icons
|
||||
import { FolderIcon, MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { BlockedIcon } from "ui/icons";
|
||||
import { BlockedIcon, LayerDiagonalIcon } from "components/icons";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
import { classNames } from "constants/common";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
|
||||
type FormInput = {
|
||||
issue_ids: string[];
|
||||
blocked_issue_ids: string[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
|
|
@ -38,11 +37,11 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
const [query, setQuery] = useState("");
|
||||
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { data: issues } = useSWR(
|
||||
workspaceSlug && projectId
|
||||
? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)
|
||||
|
|
@ -52,7 +51,16 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
: null
|
||||
);
|
||||
|
||||
const { register, handleSubmit, reset, watch: watchBlocked } = useForm<FormInput>();
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch: watchBlocked,
|
||||
setValue,
|
||||
} = useForm<FormInput>({
|
||||
defaultValues: {
|
||||
blocked_issue_ids: [],
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
setIsBlockedModalOpen(false);
|
||||
|
|
@ -60,7 +68,7 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
};
|
||||
|
||||
const onSubmit: SubmitHandler<FormInput> = (data) => {
|
||||
if (!data.issue_ids || data.issue_ids.length === 0) {
|
||||
if (!data.blocked_issue_ids || data.blocked_issue_ids.length === 0) {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
|
|
@ -69,13 +77,24 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.issue_ids)) data.issue_ids = [data.issue_ids];
|
||||
if (!Array.isArray(data.blocked_issue_ids)) data.blocked_issue_ids = [data.blocked_issue_ids];
|
||||
|
||||
const newBlocked = [...watch("blocked_list"), ...data.issue_ids];
|
||||
const newBlocked = [...watch("blocked_list"), ...data.blocked_issue_ids];
|
||||
submitChanges({ blocks_list: newBlocked });
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const filteredIssues: IIssue[] =
|
||||
query === ""
|
||||
? issuesList
|
||||
: issuesList.filter(
|
||||
(issue) =>
|
||||
issue.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||
`${issue.project_detail.identifier}-${issue.sequence_id}`
|
||||
.toLowerCase()
|
||||
.includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-start py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||
|
|
@ -123,7 +142,7 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
afterLeave={() => setQuery("")}
|
||||
appear
|
||||
>
|
||||
<Dialog as="div" className="relative z-10" onClose={handleClose}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
|
@ -136,7 +155,7 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto p-4 sm:p-6 md:p-20">
|
||||
<div className="fixed inset-0 z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
|
@ -148,16 +167,33 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
>
|
||||
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white bg-opacity-80 shadow-2xl ring-1 ring-black ring-opacity-5 backdrop-blur backdrop-filter transition-all">
|
||||
<form>
|
||||
<Combobox>
|
||||
<Combobox
|
||||
onChange={(val: string) => {
|
||||
console.log("Triggered");
|
||||
const selectedIssues = watchBlocked("blocked_issue_ids");
|
||||
if (selectedIssues.includes(val))
|
||||
setValue(
|
||||
"blocked_issue_ids",
|
||||
selectedIssues.filter((i) => i !== val)
|
||||
);
|
||||
else {
|
||||
const newBlocked = selectedIssues;
|
||||
newBlocked.push(val);
|
||||
|
||||
setValue("blocked_issue_ids", newBlocked);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative m-1">
|
||||
<MagnifyingGlassIcon
|
||||
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Combobox.Input
|
||||
<input
|
||||
type="text"
|
||||
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
|
||||
placeholder="Search..."
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -165,79 +201,72 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
static
|
||||
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto"
|
||||
>
|
||||
{issuesList.length > 0 && (
|
||||
<>
|
||||
<li className="p-2">
|
||||
{query === "" && (
|
||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
|
||||
Select blocked issues
|
||||
</h2>
|
||||
)}
|
||||
<ul className="text-sm text-gray-700">
|
||||
{issuesList.map((issue) => {
|
||||
if (
|
||||
!watch("blocked_list").includes(issue.id) &&
|
||||
!watch("blockers_list").includes(issue.id)
|
||||
) {
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="label"
|
||||
htmlFor={`blocked-issue-${issue.id}`}
|
||||
value={{
|
||||
name: issue.name,
|
||||
url: `/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`,
|
||||
}}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
"flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2",
|
||||
active ? "bg-gray-900 bg-opacity-5 text-gray-900" : ""
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register("issue_ids")}
|
||||
id={`blocked-issue-${issue.id}`}
|
||||
value={issue.id}
|
||||
/>
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||
{
|
||||
issues?.results.find((i) => i.id === issue.id)
|
||||
?.project_detail?.identifier
|
||||
}
|
||||
-{issue.sequence_id}
|
||||
</span>
|
||||
<span>{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
</>
|
||||
{filteredIssues.length > 0 ? (
|
||||
<li className="p-2">
|
||||
{query === "" && (
|
||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
|
||||
Select blocked issues
|
||||
</h2>
|
||||
)}
|
||||
<ul className="text-sm text-gray-700">
|
||||
{filteredIssues.map((issue) => {
|
||||
if (
|
||||
!watch("blocked_list").includes(issue.id) &&
|
||||
!watch("blockers_list").includes(issue.id)
|
||||
) {
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active }) =>
|
||||
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
|
||||
active ? "bg-gray-900 bg-opacity-5 text-gray-900" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={watchBlocked("blocked_issue_ids").includes(
|
||||
issue.id
|
||||
)}
|
||||
readOnly
|
||||
/>
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||
{
|
||||
issues?.results.find((i) => i.id === issue.id)
|
||||
?.project_detail?.identifier
|
||||
}
|
||||
-{issue.sequence_id}
|
||||
</span>
|
||||
<span>{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
|
||||
<LayerDiagonalIcon height="56" width="56" />
|
||||
<h3 className="text-gray-500">
|
||||
No issues found. Create a new issue with{" "}
|
||||
<pre className="inline rounded bg-gray-100 px-2 py-1">
|
||||
Ctrl/Command + I
|
||||
</pre>
|
||||
.
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Options>
|
||||
|
||||
{query !== "" && issuesList.length === 0 && (
|
||||
<div className="py-14 px-6 text-center sm:px-14">
|
||||
<FolderIcon
|
||||
className="mx-auto h-6 w-6 text-gray-900 text-opacity-40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="mt-4 text-sm text-gray-900">
|
||||
We couldn{"'"}t find any issue with that term. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Combobox>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 p-3">
|
||||
|
|
|
|||
|
|
@ -5,27 +5,26 @@ import { useRouter } from "next/router";
|
|||
|
||||
import useSWR from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { SubmitHandler, useForm, UseFormWatch } from "react-hook-form";
|
||||
// constants
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
// hooks
|
||||
import useToast from "lib/hooks/useToast";
|
||||
// services
|
||||
import issuesServices from "lib/services/issues.service";
|
||||
// headless ui
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import issuesServices from "services/issues.service";
|
||||
// ui
|
||||
import { Button } from "ui";
|
||||
import { Button } from "components/ui";
|
||||
// icons
|
||||
import { FolderIcon, MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { BlockerIcon, LayerDiagonalIcon } from "ui/icons";
|
||||
import { BlockerIcon, LayerDiagonalIcon } from "components/icons";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
import { classNames } from "constants/common";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
|
||||
type FormInput = {
|
||||
issue_ids: string[];
|
||||
blocker_issue_ids: string[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
|
|
@ -52,7 +51,16 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
: null
|
||||
);
|
||||
|
||||
const { register, handleSubmit, reset } = useForm<FormInput>();
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch: watchBlocker,
|
||||
setValue,
|
||||
} = useForm<FormInput>({
|
||||
defaultValues: {
|
||||
blocker_issue_ids: [],
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
setIsBlockerModalOpen(false);
|
||||
|
|
@ -60,7 +68,7 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
};
|
||||
|
||||
const onSubmit: SubmitHandler<FormInput> = (data) => {
|
||||
if (!data.issue_ids || data.issue_ids.length === 0) {
|
||||
if (!data.blocker_issue_ids || data.blocker_issue_ids.length === 0) {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
|
|
@ -69,13 +77,24 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.issue_ids)) data.issue_ids = [data.issue_ids];
|
||||
if (!Array.isArray(data.blocker_issue_ids)) data.blocker_issue_ids = [data.blocker_issue_ids];
|
||||
|
||||
const newBlockers = [...watch("blockers_list"), ...data.issue_ids];
|
||||
const newBlockers = [...watch("blockers_list"), ...data.blocker_issue_ids];
|
||||
submitChanges({ blockers_list: newBlockers });
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const filteredIssues: IIssue[] =
|
||||
query === ""
|
||||
? issuesList
|
||||
: issuesList.filter(
|
||||
(issue) =>
|
||||
issue.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||
`${issue.project_detail.identifier}-${issue.sequence_id}`
|
||||
.toLowerCase()
|
||||
.includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-start py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||
|
|
@ -125,7 +144,7 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
afterLeave={() => setQuery("")}
|
||||
appear
|
||||
>
|
||||
<Dialog as="div" className="relative z-10" onClose={handleClose}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
|
@ -138,7 +157,7 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto p-4 sm:p-6 md:p-20">
|
||||
<div className="fixed inset-0 z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
|
@ -149,118 +168,116 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white bg-opacity-80 shadow-2xl ring-1 ring-black ring-opacity-5 backdrop-blur backdrop-filter transition-all">
|
||||
<form>
|
||||
<Combobox>
|
||||
<div className="relative m-1">
|
||||
<MagnifyingGlassIcon
|
||||
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Combobox.Input
|
||||
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
|
||||
placeholder="Search..."
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Combobox
|
||||
onChange={(val: string) => {
|
||||
const selectedIssues = watchBlocker("blocker_issue_ids");
|
||||
if (selectedIssues.includes(val))
|
||||
setValue(
|
||||
"blocker_issue_ids",
|
||||
selectedIssues.filter((i) => i !== val)
|
||||
);
|
||||
else {
|
||||
const newBlockers = selectedIssues;
|
||||
newBlockers.push(val);
|
||||
|
||||
<Combobox.Options
|
||||
static
|
||||
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto"
|
||||
>
|
||||
{issuesList.length > 0 ? (
|
||||
<li className="p-2">
|
||||
{query === "" && (
|
||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
|
||||
Select blocker issues
|
||||
</h2>
|
||||
)}
|
||||
<ul className="text-sm text-gray-700">
|
||||
{issuesList.map((issue) => {
|
||||
if (
|
||||
!watch("blockers_list").includes(issue.id) &&
|
||||
!watch("blocked_list").includes(issue.id)
|
||||
)
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="label"
|
||||
htmlFor={`blocker-issue-${issue.id}`}
|
||||
value={{
|
||||
name: issue.name,
|
||||
url: `/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`,
|
||||
}}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
"flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2",
|
||||
active ? "bg-gray-900 bg-opacity-5 text-gray-900" : ""
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
{...register("issue_ids")}
|
||||
id={`blocker-issue-${issue.id}`}
|
||||
value={issue.id}
|
||||
/>
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||
{
|
||||
issues?.results.find((i) => i.id === issue.id)
|
||||
?.project_detail?.identifier
|
||||
}
|
||||
-{issue.sequence_id}
|
||||
</span>
|
||||
<span>{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
|
||||
<LayerDiagonalIcon height="56" width="56" />
|
||||
<h3 className="text-gray-500">
|
||||
No issues found. Create a new issue with{" "}
|
||||
<pre className="inline rounded bg-gray-100 px-2 py-1">
|
||||
Ctrl/Command + I
|
||||
</pre>
|
||||
.
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Options>
|
||||
setValue("blocker_issue_ids", newBlockers);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative m-1">
|
||||
<MagnifyingGlassIcon
|
||||
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
|
||||
placeholder="Search..."
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{query !== "" && issuesList.length === 0 && (
|
||||
<div className="py-14 px-6 text-center sm:px-14">
|
||||
<FolderIcon
|
||||
className="mx-auto h-6 w-6 text-gray-900 text-opacity-40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p className="mt-4 text-sm text-gray-900">
|
||||
We couldn{"'"}t find any issue with that term. Please try again.
|
||||
</p>
|
||||
<Combobox.Options
|
||||
static
|
||||
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto"
|
||||
>
|
||||
{filteredIssues.length > 0 ? (
|
||||
<li className="p-2">
|
||||
{query === "" && (
|
||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
|
||||
Select blocker issues
|
||||
</h2>
|
||||
)}
|
||||
<ul className="text-sm text-gray-700">
|
||||
{filteredIssues.map((issue) => {
|
||||
if (
|
||||
!watch("blockers_list").includes(issue.id) &&
|
||||
!watch("blocked_list").includes(issue.id)
|
||||
)
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active }) =>
|
||||
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
|
||||
active ? "bg-gray-900 bg-opacity-5 text-gray-900" : ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={watchBlocker("blocker_issue_ids").includes(
|
||||
issue.id
|
||||
)}
|
||||
readOnly
|
||||
/>
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||
{
|
||||
issues?.results.find((i) => i.id === issue.id)
|
||||
?.project_detail?.identifier
|
||||
}
|
||||
-{issue.sequence_id}
|
||||
</span>
|
||||
<span>{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
|
||||
<LayerDiagonalIcon height="56" width="56" />
|
||||
<h3 className="text-gray-500">
|
||||
No issues found. Create a new issue with{" "}
|
||||
<pre className="inline rounded bg-gray-100 px-2 py-1">
|
||||
Ctrl/Command + I
|
||||
</pre>
|
||||
.
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
</Combobox>
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 p-3">
|
||||
<div>
|
||||
<Button type="button" theme="secondary" size="sm" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={handleSubmit(onSubmit)} size="sm">
|
||||
Add selected issues
|
||||
<div className="flex items-center justify-end gap-2 p-3">
|
||||
<div>
|
||||
<Button type="button" theme="secondary" size="sm" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<Button onClick={handleSubmit(onSubmit)} size="sm">
|
||||
Add selected issues
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,29 +5,26 @@ import { useRouter } from "next/router";
|
|||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
import { Control, Controller, UseFormWatch } from "react-hook-form";
|
||||
import { UseFormWatch } from "react-hook-form";
|
||||
// constants
|
||||
import { CYCLE_ISSUES, CYCLE_LIST, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
// services
|
||||
import issuesService from "lib/services/issues.service";
|
||||
import cyclesService from "lib/services/cycles.service";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "ui";
|
||||
// icons
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/outline";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
import cyclesService from "services/cycles.service";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "components/ui";
|
||||
// icons
|
||||
// types
|
||||
import { CycleIssueResponse, ICycle, IIssue, IssueResponse } from "types";
|
||||
// common
|
||||
import { classNames } from "constants/common";
|
||||
import { ICycle, IIssue } from "types";
|
||||
import { CYCLE_ISSUES, CYCLE_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
issueDetail: IIssue | undefined;
|
||||
control: Control<IIssue, any>;
|
||||
handleCycleChange: (cycle: ICycle) => void;
|
||||
watch: UseFormWatch<IIssue>;
|
||||
};
|
||||
|
||||
const SelectCycle: React.FC<Props> = ({ issueDetail, control, handleCycleChange }) => {
|
||||
const SelectCycle: React.FC<Props> = ({ issueDetail, handleCycleChange }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
|
|
@ -41,12 +38,10 @@ const SelectCycle: React.FC<Props> = ({ issueDetail, control, handleCycleChange
|
|||
const removeIssueFromCycle = (bridgeId: string, cycleId: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
|
||||
|
||||
issuesService
|
||||
.removeIssueFromCycle(workspaceSlug as string, projectId as string, cycleId, bridgeId)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
mutate(ISSUE_DETAILS(issueId as string));
|
||||
|
||||
mutate(CYCLE_ISSUES(cycleId));
|
||||
})
|
||||
|
|
@ -55,6 +50,8 @@ const SelectCycle: React.FC<Props> = ({ issueDetail, control, handleCycleChange
|
|||
});
|
||||
};
|
||||
|
||||
const issueCycle = issueDetail?.issue_cycle;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||
|
|
@ -62,54 +59,40 @@ const SelectCycle: React.FC<Props> = ({ issueDetail, control, handleCycleChange
|
|||
<p>Cycle</p>
|
||||
</div>
|
||||
<div className="space-y-1 sm:basis-1/2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="issue_cycle"
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate text-left sm:block"
|
||||
)}
|
||||
>
|
||||
{value ? value?.cycle_detail?.name : "None"}
|
||||
</span>
|
||||
}
|
||||
value={value}
|
||||
onChange={(value: any) => {
|
||||
value === null
|
||||
? removeIssueFromCycle(
|
||||
issueDetail?.issue_cycle?.id ?? "",
|
||||
issueDetail?.issue_cycle?.cycle ?? ""
|
||||
)
|
||||
: handleCycleChange(cycles?.find((c) => c.id === value) as any);
|
||||
}}
|
||||
>
|
||||
{cycles ? (
|
||||
cycles.length > 0 ? (
|
||||
<>
|
||||
<CustomSelect.Option value={null} className="capitalize">
|
||||
<>None</>
|
||||
</CustomSelect.Option>
|
||||
{cycles.map((option) => (
|
||||
<CustomSelect.Option key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center">No cycles found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</CustomSelect>
|
||||
</>
|
||||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={`hidden truncate text-left sm:block ${issueCycle ? "" : "text-gray-900"}`}
|
||||
>
|
||||
{issueCycle ? issueCycle.cycle_detail.name : "None"}
|
||||
</span>
|
||||
}
|
||||
value={issueCycle?.cycle_detail.id}
|
||||
onChange={(value: any) => {
|
||||
value === null
|
||||
? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "")
|
||||
: handleCycleChange(cycles?.find((c) => c.id === value) as any);
|
||||
}}
|
||||
>
|
||||
{cycles ? (
|
||||
cycles.length > 0 ? (
|
||||
<>
|
||||
<CustomSelect.Option value={null} className="capitalize">
|
||||
None
|
||||
</CustomSelect.Option>
|
||||
{cycles.map((option) => (
|
||||
<CustomSelect.Option key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center">No cycles found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
/>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,17 +6,15 @@ import useSWR from "swr";
|
|||
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// constants
|
||||
import { MODULE_LIST } from "constants/fetch-keys";
|
||||
// services
|
||||
import modulesService from "lib/services/modules.service";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "ui";
|
||||
// icons
|
||||
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
|
||||
// services
|
||||
import modulesService from "services/modules.service";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "components/ui";
|
||||
// icons
|
||||
// types
|
||||
import { IIssue, IModule } from "types";
|
||||
// common
|
||||
import { classNames } from "constants/common";
|
||||
import { MODULE_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
control: Control<IIssue, any>;
|
||||
|
|
@ -48,10 +46,7 @@ const SelectModule: React.FC<Props> = ({ control, handleModuleChange }) => {
|
|||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate text-left sm:block"
|
||||
)}
|
||||
className={`hidden truncate text-left sm:block ${value ? "" : "text-gray-900"}`}
|
||||
>
|
||||
{value ? modules?.find((m) => m.id === value?.module_detail.id)?.name : "None"}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ import useSWR from "swr";
|
|||
|
||||
import { Control, Controller, UseFormWatch } from "react-hook-form";
|
||||
// fetch keys
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
import { UserIcon } from "@heroicons/react/24/outline";
|
||||
// services
|
||||
import issuesServices from "lib/services/issues.service";
|
||||
import issuesServices from "services/issues.service";
|
||||
// components
|
||||
import IssuesListModal from "components/project/issues/issues-list-modal";
|
||||
// icons
|
||||
import { UserIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
control: Control<IIssue, any>;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ import React from "react";
|
|||
// react-hook-form
|
||||
import { Control, Controller, UseFormWatch } from "react-hook-form";
|
||||
// ui
|
||||
import { CustomSelect } from "ui";
|
||||
// icons
|
||||
import { ChartBarIcon } from "@heroicons/react/24/outline";
|
||||
import { CustomSelect } from "components/ui";
|
||||
// icons
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// common
|
||||
import { classNames } from "constants/common";
|
||||
// constants
|
||||
import { getPriorityIcon } from "constants/global";
|
||||
import { PRIORITIES } from "constants/";
|
||||
|
|
@ -20,54 +19,49 @@ type Props = {
|
|||
watch: UseFormWatch<IIssue>;
|
||||
};
|
||||
|
||||
const SelectPriority: React.FC<Props> = ({ control, submitChanges, watch }) => {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||
<ChartBarIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Priority</p>
|
||||
</div>
|
||||
<div className="sm:basis-1/2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="state"
|
||||
render={({ field: { value } }) => (
|
||||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"flex items-center gap-2 text-left capitalize"
|
||||
)}
|
||||
>
|
||||
{getPriorityIcon(
|
||||
watch("priority") && watch("priority") !== ""
|
||||
? watch("priority") ?? ""
|
||||
: "None",
|
||||
"text-sm"
|
||||
)}
|
||||
{watch("priority") && watch("priority") !== "" ? watch("priority") : "None"}
|
||||
</span>
|
||||
}
|
||||
value={value}
|
||||
onChange={(value: any) => {
|
||||
submitChanges({ priority: value });
|
||||
}}
|
||||
>
|
||||
{PRIORITIES.map((option) => (
|
||||
<CustomSelect.Option key={option} value={option} className="capitalize">
|
||||
<>
|
||||
{getPriorityIcon(option, "text-sm")}
|
||||
{option ?? "None"}
|
||||
</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
const SelectPriority: React.FC<Props> = ({ control, submitChanges, watch }) => (
|
||||
<div className="flex flex-wrap items-center py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||
<ChartBarIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<p>Priority</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<div className="sm:basis-1/2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="state"
|
||||
render={({ field: { value } }) => (
|
||||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={`flex items-center gap-2 text-left capitalize ${
|
||||
value ? "" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{getPriorityIcon(
|
||||
watch("priority") && watch("priority") !== "" ? watch("priority") ?? "" : "None",
|
||||
"text-sm"
|
||||
)}
|
||||
{watch("priority") && watch("priority") !== "" ? watch("priority") : "None"}
|
||||
</span>
|
||||
}
|
||||
value={value}
|
||||
onChange={(value: any) => {
|
||||
submitChanges({ priority: value });
|
||||
}}
|
||||
>
|
||||
{PRIORITIES.map((option) => (
|
||||
<CustomSelect.Option key={option} value={option} className="capitalize">
|
||||
<>
|
||||
{getPriorityIcon(option, "text-sm")}
|
||||
{option ?? "None"}
|
||||
</>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SelectPriority;
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@ import useSWR from "swr";
|
|||
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// services
|
||||
import stateService from "lib/services/state.service";
|
||||
// icons
|
||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||
// constants
|
||||
import { classNames } from "constants/common";
|
||||
import { STATE_LIST } from "constants/fetch-keys";
|
||||
import stateService from "services/state.service";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "components/ui";
|
||||
// icons
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// ui
|
||||
import { Spinner, CustomSelect } from "ui";
|
||||
// constants
|
||||
import { STATE_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
control: Control<IIssue, any>;
|
||||
|
|
@ -47,10 +46,7 @@ const SelectState: React.FC<Props> = ({ control, submitChanges }) => {
|
|||
<CustomSelect
|
||||
label={
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"flex items-center gap-2 text-left"
|
||||
)}
|
||||
className={`flex items-center gap-2 text-left ${value ? "" : "text-gray-900"}`}
|
||||
>
|
||||
{value ? (
|
||||
<>
|
||||
|
|
@ -59,7 +55,7 @@ const SelectState: React.FC<Props> = ({ control, submitChanges }) => {
|
|||
style={{
|
||||
backgroundColor: states?.find((option) => option.id === value)?.color,
|
||||
}}
|
||||
></span>
|
||||
/>
|
||||
{states?.find((option) => option.id === value)?.name}
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -81,7 +77,7 @@ const SelectState: React.FC<Props> = ({ control, submitChanges }) => {
|
|||
<span
|
||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: option.color }}
|
||||
></span>
|
||||
/>
|
||||
)}
|
||||
{option.name}
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue