mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: bugs fixed
This commit is contained in:
parent
c4079c4e0c
commit
a2db04f9ff
5
apps/app/components/common/board-view/single-board.tsx
Normal file
5
apps/app/components/common/board-view/single-board.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const SingleBoard = () => {
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SingleBoard;
|
@ -117,7 +117,7 @@ const SingleIssue: React.FC<Props> = ({
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
{PRIORITIES?.map((priority) => (
|
{PRIORITIES?.map((priority) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={priority}
|
key={priority}
|
||||||
@ -186,18 +186,24 @@ const SingleIssue: React.FC<Props> = ({
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
{states?.map((state) => (
|
{states?.map((state) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={state.id}
|
key={state.id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
classNames(
|
classNames(
|
||||||
active ? "bg-indigo-50" : "bg-white",
|
active ? "bg-indigo-50" : "bg-white",
|
||||||
"cursor-pointer select-none px-3 py-2"
|
"flex items-center gap-2 cursor-pointer select-none px-3 py-2"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
value={state.id}
|
value={state.id}
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: state.color,
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
{addSpaceIfCamelCase(state.name)}
|
{addSpaceIfCamelCase(state.name)}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
))}
|
))}
|
||||||
@ -316,7 +322,7 @@ const SingleIssue: React.FC<Props> = ({
|
|||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute left-0 z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute left-0 z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
{people?.map((person) => (
|
{people?.map((person) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={person.id}
|
key={person.id}
|
@ -18,6 +18,14 @@ type Props = {
|
|||||||
removeIssueFromCycle: (bridgeId: string) => void;
|
removeIssueFromCycle: (bridgeId: string) => void;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
||||||
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
|
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||||
|
setPreloadedData: React.Dispatch<
|
||||||
|
React.SetStateAction<
|
||||||
|
| (Partial<IIssue> & {
|
||||||
|
actionType: "createIssue" | "edit" | "delete";
|
||||||
|
})
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CyclesBoardView: React.FC<Props> = ({
|
const CyclesBoardView: React.FC<Props> = ({
|
||||||
@ -30,6 +38,7 @@ const CyclesBoardView: React.FC<Props> = ({
|
|||||||
removeIssueFromCycle,
|
removeIssueFromCycle,
|
||||||
partialUpdateIssue,
|
partialUpdateIssue,
|
||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
|
setPreloadedData,
|
||||||
}) => {
|
}) => {
|
||||||
const { states } = useUser();
|
const { states } = useUser();
|
||||||
|
|
||||||
@ -63,6 +72,12 @@ const CyclesBoardView: React.FC<Props> = ({
|
|||||||
openCreateIssueModal={openCreateIssueModal}
|
openCreateIssueModal={openCreateIssueModal}
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
|
setPreloadedData={setPreloadedData}
|
||||||
|
stateId={
|
||||||
|
selectedGroup === "state_detail.name"
|
||||||
|
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ import workspaceService from "lib/services/workspace.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// components
|
// components
|
||||||
import SingleIssue from "components/project/common/board-view/single-issue";
|
import SingleIssue from "components/common/board-view/single-issue";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
// ui
|
// ui
|
||||||
@ -35,6 +35,15 @@ type Props = {
|
|||||||
removeIssueFromCycle: (bridgeId: string) => void;
|
removeIssueFromCycle: (bridgeId: string) => void;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
||||||
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
|
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||||
|
setPreloadedData: React.Dispatch<
|
||||||
|
React.SetStateAction<
|
||||||
|
| (Partial<IIssue> & {
|
||||||
|
actionType: "createIssue" | "edit" | "delete";
|
||||||
|
})
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
stateId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SingleCycleBoard: React.FC<Props> = ({
|
const SingleCycleBoard: React.FC<Props> = ({
|
||||||
@ -49,6 +58,8 @@ const SingleCycleBoard: React.FC<Props> = ({
|
|||||||
removeIssueFromCycle,
|
removeIssueFromCycle,
|
||||||
partialUpdateIssue,
|
partialUpdateIssue,
|
||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
|
setPreloadedData,
|
||||||
|
stateId,
|
||||||
}) => {
|
}) => {
|
||||||
// Collapse/Expand
|
// Collapse/Expand
|
||||||
const [show, setState] = useState(true);
|
const [show, setState] = useState(true);
|
||||||
@ -109,7 +120,18 @@ const SingleCycleBoard: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomMenu width="auto" ellipsis>
|
<CustomMenu width="auto" ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={() => openCreateIssueModal()}>
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
openCreateIssueModal();
|
||||||
|
if (selectedGroup !== null) {
|
||||||
|
setPreloadedData({
|
||||||
|
state: stateId !== null ? stateId : undefined,
|
||||||
|
[selectedGroup]: groupTitle,
|
||||||
|
actionType: "createIssue",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
Create new
|
Create new
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
|
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
|
||||||
@ -150,55 +172,35 @@ const SingleCycleBoard: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Menu as="div" className="relative text-left">
|
<CustomMenu
|
||||||
<Menu.Button className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 text-xs font-medium">
|
label={
|
||||||
<PlusIcon className="h-3 w-3" />
|
<span className="flex items-center gap-1">
|
||||||
Add issue
|
<PlusIcon className="h-3 w-3" />
|
||||||
</Menu.Button>
|
Add issue
|
||||||
|
</span>
|
||||||
<Transition
|
}
|
||||||
as={React.Fragment}
|
className="mt-1"
|
||||||
enter="transition ease-out duration-100"
|
optionsPosition="left"
|
||||||
enterFrom="transform opacity-0 scale-95"
|
withoutBorder
|
||||||
enterTo="transform opacity-100 scale-100"
|
>
|
||||||
leave="transition ease-in duration-75"
|
<CustomMenu.MenuItem
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
onClick={() => {
|
||||||
leaveTo="transform opacity-0 scale-95"
|
openCreateIssueModal();
|
||||||
|
if (selectedGroup !== null) {
|
||||||
|
setPreloadedData({
|
||||||
|
state: stateId !== null ? stateId : undefined,
|
||||||
|
[selectedGroup]: groupTitle,
|
||||||
|
actionType: "createIssue",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute left-0 z-10 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none whitespace-nowrap">
|
Create new
|
||||||
<div className="py-1">
|
</CustomMenu.MenuItem>
|
||||||
<Menu.Item>
|
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
|
||||||
{({ active }) => (
|
Add an existing issue
|
||||||
<button
|
</CustomMenu.MenuItem>
|
||||||
type="button"
|
</CustomMenu>
|
||||||
className={classNames(
|
|
||||||
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
|
|
||||||
"block w-full p-2 text-left"
|
|
||||||
)}
|
|
||||||
onClick={() => openCreateIssueModal()}
|
|
||||||
>
|
|
||||||
Create new
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
|
|
||||||
"block w-full p-2 text-left"
|
|
||||||
)}
|
|
||||||
onClick={() => openIssuesListModal()}
|
|
||||||
>
|
|
||||||
Add an existing issue
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
</div>
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,13 +28,21 @@ import workspaceService from "lib/services/workspace.service";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
groupedByIssues: {
|
groupedByIssues: {
|
||||||
[key: string]: IIssue[];
|
[key: string]: (IIssue & { bridge?: string })[];
|
||||||
};
|
};
|
||||||
properties: Properties;
|
properties: Properties;
|
||||||
selectedGroup: NestedKeyOf<IIssue> | null;
|
selectedGroup: NestedKeyOf<IIssue> | null;
|
||||||
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
|
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
|
||||||
openIssuesListModal: () => void;
|
openIssuesListModal: () => void;
|
||||||
removeIssueFromCycle: (bridgeId: string) => void;
|
removeIssueFromCycle: (bridgeId: string) => void;
|
||||||
|
setPreloadedData: React.Dispatch<
|
||||||
|
React.SetStateAction<
|
||||||
|
| (Partial<IIssue> & {
|
||||||
|
actionType: "createIssue" | "edit" | "delete";
|
||||||
|
})
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CyclesListView: React.FC<Props> = ({
|
const CyclesListView: React.FC<Props> = ({
|
||||||
@ -44,8 +52,9 @@ const CyclesListView: React.FC<Props> = ({
|
|||||||
openIssuesListModal,
|
openIssuesListModal,
|
||||||
properties,
|
properties,
|
||||||
removeIssueFromCycle,
|
removeIssueFromCycle,
|
||||||
|
setPreloadedData,
|
||||||
}) => {
|
}) => {
|
||||||
const { activeWorkspace, activeProject } = useUser();
|
const { activeWorkspace, activeProject, states } = useUser();
|
||||||
|
|
||||||
const { data: people } = useSWR<IWorkspaceMember[]>(
|
const { data: people } = useSWR<IWorkspaceMember[]>(
|
||||||
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
activeWorkspace ? WORKSPACE_MEMBERS : null,
|
||||||
@ -54,273 +63,261 @@ const CyclesListView: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-5">
|
<div className="flex flex-col space-y-5">
|
||||||
{Object.keys(groupedByIssues).map((singleGroup) => (
|
{Object.keys(groupedByIssues).map((singleGroup) => {
|
||||||
<Disclosure key={singleGroup} as="div" defaultOpen>
|
const stateId =
|
||||||
{({ open }) => (
|
selectedGroup === "state_detail.name"
|
||||||
<div className="bg-white rounded-lg">
|
? states?.find((s) => s.name === singleGroup)?.id ?? null
|
||||||
<div className="bg-gray-100 px-4 py-3 rounded-t-lg">
|
: null;
|
||||||
<Disclosure.Button>
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<span>
|
|
||||||
<ChevronDownIcon
|
|
||||||
className={`h-4 w-4 text-gray-500 ${!open ? "transform -rotate-90" : ""}`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{selectedGroup !== null ? (
|
|
||||||
<h2 className="font-medium leading-5 capitalize">
|
|
||||||
{singleGroup === null || singleGroup === "null"
|
|
||||||
? selectedGroup === "priority" && "No priority"
|
|
||||||
: addSpaceIfCamelCase(singleGroup)}
|
|
||||||
</h2>
|
|
||||||
) : (
|
|
||||||
<h2 className="font-medium leading-5">All Issues</h2>
|
|
||||||
)}
|
|
||||||
<p className="text-gray-500 text-sm">
|
|
||||||
{groupedByIssues[singleGroup as keyof IIssue].length}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Disclosure.Button>
|
|
||||||
</div>
|
|
||||||
<Transition
|
|
||||||
show={open}
|
|
||||||
enter="transition duration-100 ease-out"
|
|
||||||
enterFrom="transform opacity-0"
|
|
||||||
enterTo="transform opacity-100"
|
|
||||||
leave="transition duration-75 ease-out"
|
|
||||||
leaveFrom="transform opacity-100"
|
|
||||||
leaveTo="transform opacity-0"
|
|
||||||
>
|
|
||||||
<Disclosure.Panel>
|
|
||||||
<div className="divide-y-2">
|
|
||||||
{groupedByIssues[singleGroup] ? (
|
|
||||||
groupedByIssues[singleGroup].length > 0 ? (
|
|
||||||
groupedByIssues[singleGroup].map((issue: IIssue) => {
|
|
||||||
const assignees = [
|
|
||||||
...(issue?.assignees_list ?? []),
|
|
||||||
...(issue?.assignees ?? []),
|
|
||||||
]?.map((assignee) => {
|
|
||||||
const tempPerson = people?.find(
|
|
||||||
(p) => p.member.id === assignee
|
|
||||||
)?.member;
|
|
||||||
|
|
||||||
return {
|
return (
|
||||||
avatar: tempPerson?.avatar,
|
<Disclosure key={singleGroup} as="div" defaultOpen>
|
||||||
first_name: tempPerson?.first_name,
|
{({ open }) => (
|
||||||
email: tempPerson?.email,
|
<div className="bg-white rounded-lg">
|
||||||
};
|
<div className="bg-gray-100 px-4 py-3 rounded-t-lg">
|
||||||
});
|
<Disclosure.Button>
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
return (
|
<span>
|
||||||
<div
|
<ChevronDownIcon
|
||||||
key={issue.id}
|
className={`h-4 w-4 text-gray-500 ${!open ? "transform -rotate-90" : ""}`}
|
||||||
className="px-4 py-3 text-sm rounded flex justify-between items-center gap-2"
|
/>
|
||||||
>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
{selectedGroup !== null ? (
|
||||||
<span
|
<h2 className="font-medium leading-5 capitalize">
|
||||||
className={`flex-shrink-0 h-1.5 w-1.5 block rounded-full`}
|
{singleGroup === null || singleGroup === "null"
|
||||||
style={{
|
? selectedGroup === "priority" && "No priority"
|
||||||
backgroundColor: issue.state_detail.color,
|
: addSpaceIfCamelCase(singleGroup)}
|
||||||
}}
|
</h2>
|
||||||
/>
|
|
||||||
<Link href={`/projects/${activeProject?.id}/issues/${issue.id}`}>
|
|
||||||
<a className="group relative flex items-center gap-2">
|
|
||||||
{properties.key && (
|
|
||||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
|
||||||
{activeProject?.identifier}-{issue.sequence_id}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="">{issue.name}</span>
|
|
||||||
<div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
|
|
||||||
<h5 className="font-medium mb-1">Name</h5>
|
|
||||||
<div>{issue.name}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex-shrink-0 flex items-center gap-x-1 gap-y-2 text-xs flex-wrap">
|
|
||||||
{properties.priority && (
|
|
||||||
<div
|
|
||||||
className={`group relative flex-shrink-0 flex items-center gap-1 text-xs rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 capitalize ${
|
|
||||||
issue.priority === "urgent"
|
|
||||||
? "bg-red-100 text-red-600"
|
|
||||||
: issue.priority === "high"
|
|
||||||
? "bg-orange-100 text-orange-500"
|
|
||||||
: issue.priority === "medium"
|
|
||||||
? "bg-yellow-100 text-yellow-500"
|
|
||||||
: issue.priority === "low"
|
|
||||||
? "bg-green-100 text-green-500"
|
|
||||||
: "bg-gray-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* {getPriorityIcon(issue.priority ?? "")} */}
|
|
||||||
{issue.priority ?? "None"}
|
|
||||||
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
|
||||||
<h5 className="font-medium mb-1 text-gray-900">Priority</h5>
|
|
||||||
<div
|
|
||||||
className={`capitalize ${
|
|
||||||
issue.priority === "urgent"
|
|
||||||
? "text-red-600"
|
|
||||||
: issue.priority === "high"
|
|
||||||
? "text-orange-500"
|
|
||||||
: issue.priority === "medium"
|
|
||||||
? "text-yellow-500"
|
|
||||||
: issue.priority === "low"
|
|
||||||
? "text-green-500"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{issue.priority ?? "None"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{properties.state && (
|
|
||||||
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
|
|
||||||
<span
|
|
||||||
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: issue?.state_detail?.color,
|
|
||||||
}}
|
|
||||||
></span>
|
|
||||||
{addSpaceIfCamelCase(issue?.state_detail.name)}
|
|
||||||
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
|
||||||
<h5 className="font-medium mb-1">State</h5>
|
|
||||||
<div>{issue?.state_detail.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{properties.start_date && (
|
|
||||||
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
|
|
||||||
<CalendarDaysIcon className="h-4 w-4" />
|
|
||||||
{issue.start_date
|
|
||||||
? renderShortNumericDateFormat(issue.start_date)
|
|
||||||
: "N/A"}
|
|
||||||
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
|
||||||
<h5 className="font-medium mb-1">Started at</h5>
|
|
||||||
<div>
|
|
||||||
{renderShortNumericDateFormat(issue.start_date ?? "")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{properties.target_date && (
|
|
||||||
<div
|
|
||||||
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
|
|
||||||
issue.target_date === null
|
|
||||||
? ""
|
|
||||||
: issue.target_date < new Date().toISOString()
|
|
||||||
? "text-red-600"
|
|
||||||
: findHowManyDaysLeft(issue.target_date) <= 3 &&
|
|
||||||
"text-orange-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<CalendarDaysIcon className="h-4 w-4" />
|
|
||||||
{issue.target_date
|
|
||||||
? renderShortNumericDateFormat(issue.target_date)
|
|
||||||
: "N/A"}
|
|
||||||
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
|
||||||
<h5 className="font-medium mb-1 text-gray-900">
|
|
||||||
Target date
|
|
||||||
</h5>
|
|
||||||
<div>
|
|
||||||
{renderShortNumericDateFormat(issue.target_date ?? "")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{issue.target_date &&
|
|
||||||
(issue.target_date < new Date().toISOString()
|
|
||||||
? `Target date has passed by ${findHowManyDaysLeft(
|
|
||||||
issue.target_date
|
|
||||||
)} days`
|
|
||||||
: findHowManyDaysLeft(issue.target_date) <= 3
|
|
||||||
? `Target date is in ${findHowManyDaysLeft(
|
|
||||||
issue.target_date
|
|
||||||
)} days`
|
|
||||||
: "Target date")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<CustomMenu width="auto" ellipsis>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => openCreateIssueModal(issue, "edit")}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => removeIssueFromCycle(issue.bridge ?? "")}
|
|
||||||
>
|
|
||||||
Remove from cycle
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem>Delete permanently</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm px-4 py-3 text-gray-500">No issues.</p>
|
<h2 className="font-medium leading-5">All Issues</h2>
|
||||||
)
|
)}
|
||||||
) : (
|
<p className="text-gray-500 text-sm">
|
||||||
<div className="h-full w-full flex items-center justify-center">
|
{groupedByIssues[singleGroup as keyof IIssue].length}
|
||||||
<Spinner />
|
</p>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Disclosure.Panel>
|
|
||||||
</Transition>
|
|
||||||
<Menu as="div" className="relative text-left p-3">
|
|
||||||
<Menu.Button className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 text-xs font-medium">
|
|
||||||
<PlusIcon className="h-3 w-3" />
|
|
||||||
Add issue
|
|
||||||
</Menu.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<Menu.Items className="absolute left-0 z-10 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none whitespace-nowrap">
|
|
||||||
<div className="py-1">
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
|
|
||||||
"block w-full p-2 text-left"
|
|
||||||
)}
|
|
||||||
onClick={() => openCreateIssueModal()}
|
|
||||||
>
|
|
||||||
Create new
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
|
|
||||||
"block w-full p-2 text-left"
|
|
||||||
)}
|
|
||||||
onClick={() => openIssuesListModal()}
|
|
||||||
>
|
|
||||||
Add an existing issue
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
</div>
|
</div>
|
||||||
</Menu.Items>
|
</Disclosure.Button>
|
||||||
|
</div>
|
||||||
|
<Transition
|
||||||
|
show={open}
|
||||||
|
enter="transition duration-100 ease-out"
|
||||||
|
enterFrom="transform opacity-0"
|
||||||
|
enterTo="transform opacity-100"
|
||||||
|
leave="transition duration-75 ease-out"
|
||||||
|
leaveFrom="transform opacity-100"
|
||||||
|
leaveTo="transform opacity-0"
|
||||||
|
>
|
||||||
|
<Disclosure.Panel>
|
||||||
|
<div className="divide-y-2">
|
||||||
|
{groupedByIssues[singleGroup] ? (
|
||||||
|
groupedByIssues[singleGroup].length > 0 ? (
|
||||||
|
groupedByIssues[singleGroup].map((issue) => {
|
||||||
|
const assignees = [
|
||||||
|
...(issue?.assignees_list ?? []),
|
||||||
|
...(issue?.assignees ?? []),
|
||||||
|
]?.map((assignee) => {
|
||||||
|
const tempPerson = people?.find(
|
||||||
|
(p) => p.member.id === assignee
|
||||||
|
)?.member;
|
||||||
|
|
||||||
|
return {
|
||||||
|
avatar: tempPerson?.avatar,
|
||||||
|
first_name: tempPerson?.first_name,
|
||||||
|
email: tempPerson?.email,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={issue.id}
|
||||||
|
className="px-4 py-3 text-sm rounded flex justify-between items-center gap-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className={`flex-shrink-0 h-1.5 w-1.5 block rounded-full`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: issue.state_detail.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Link href={`/projects/${activeProject?.id}/issues/${issue.id}`}>
|
||||||
|
<a className="group relative flex items-center gap-2">
|
||||||
|
{properties.key && (
|
||||||
|
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||||
|
{activeProject?.identifier}-{issue.sequence_id}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>{issue.name}</span>
|
||||||
|
{/* <div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
|
||||||
|
<h5 className="font-medium mb-1">Name</h5>
|
||||||
|
<div>{issue.name}</div>
|
||||||
|
</div> */}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 flex items-center gap-x-1 gap-y-2 text-xs flex-wrap">
|
||||||
|
{properties.priority && (
|
||||||
|
<div
|
||||||
|
className={`group relative flex-shrink-0 flex items-center gap-1 text-xs rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 capitalize ${
|
||||||
|
issue.priority === "urgent"
|
||||||
|
? "bg-red-100 text-red-600"
|
||||||
|
: issue.priority === "high"
|
||||||
|
? "bg-orange-100 text-orange-500"
|
||||||
|
: issue.priority === "medium"
|
||||||
|
? "bg-yellow-100 text-yellow-500"
|
||||||
|
: issue.priority === "low"
|
||||||
|
? "bg-green-100 text-green-500"
|
||||||
|
: "bg-gray-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* {getPriorityIcon(issue.priority ?? "")} */}
|
||||||
|
{issue.priority ?? "None"}
|
||||||
|
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
||||||
|
<h5 className="font-medium mb-1 text-gray-900">Priority</h5>
|
||||||
|
<div
|
||||||
|
className={`capitalize ${
|
||||||
|
issue.priority === "urgent"
|
||||||
|
? "text-red-600"
|
||||||
|
: issue.priority === "high"
|
||||||
|
? "text-orange-500"
|
||||||
|
: issue.priority === "medium"
|
||||||
|
? "text-yellow-500"
|
||||||
|
: issue.priority === "low"
|
||||||
|
? "text-green-500"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{issue.priority ?? "None"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{properties.state && (
|
||||||
|
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
|
||||||
|
<span
|
||||||
|
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: issue?.state_detail?.color,
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
{addSpaceIfCamelCase(issue?.state_detail.name)}
|
||||||
|
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
||||||
|
<h5 className="font-medium mb-1">State</h5>
|
||||||
|
<div>{issue?.state_detail.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{properties.start_date && (
|
||||||
|
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
|
||||||
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
|
{issue.start_date
|
||||||
|
? renderShortNumericDateFormat(issue.start_date)
|
||||||
|
: "N/A"}
|
||||||
|
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
||||||
|
<h5 className="font-medium mb-1">Started at</h5>
|
||||||
|
<div>
|
||||||
|
{renderShortNumericDateFormat(issue.start_date ?? "")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{properties.target_date && (
|
||||||
|
<div
|
||||||
|
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
|
||||||
|
issue.target_date === null
|
||||||
|
? ""
|
||||||
|
: issue.target_date < new Date().toISOString()
|
||||||
|
? "text-red-600"
|
||||||
|
: findHowManyDaysLeft(issue.target_date) <= 3 &&
|
||||||
|
"text-orange-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
|
{issue.target_date
|
||||||
|
? renderShortNumericDateFormat(issue.target_date)
|
||||||
|
: "N/A"}
|
||||||
|
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
|
||||||
|
<h5 className="font-medium mb-1 text-gray-900">
|
||||||
|
Target date
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
{renderShortNumericDateFormat(issue.target_date ?? "")}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{issue.target_date &&
|
||||||
|
(issue.target_date < new Date().toISOString()
|
||||||
|
? `Target date has passed by ${findHowManyDaysLeft(
|
||||||
|
issue.target_date
|
||||||
|
)} days`
|
||||||
|
: findHowManyDaysLeft(issue.target_date) <= 3
|
||||||
|
? `Target date is in ${findHowManyDaysLeft(
|
||||||
|
issue.target_date
|
||||||
|
)} days`
|
||||||
|
: "Target date")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<CustomMenu width="auto" ellipsis>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => openCreateIssueModal(issue, "edit")}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => removeIssueFromCycle(issue.bridge ?? "")}
|
||||||
|
>
|
||||||
|
Remove from cycle
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem>Delete permanently</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<p className="text-sm px-4 py-3 text-gray-500">No issues.</p>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="h-full w-full flex items-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Menu>
|
<div className="p-3">
|
||||||
</div>
|
<CustomMenu
|
||||||
)}
|
label={
|
||||||
</Disclosure>
|
<span className="flex items-center gap-1">
|
||||||
))}
|
<PlusIcon className="h-3 w-3" />
|
||||||
|
Add issue
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
optionsPosition="left"
|
||||||
|
withoutBorder
|
||||||
|
>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
openCreateIssueModal();
|
||||||
|
if (selectedGroup !== null) {
|
||||||
|
setPreloadedData({
|
||||||
|
state: stateId !== null ? stateId : undefined,
|
||||||
|
[selectedGroup]: singleGroup,
|
||||||
|
actionType: "createIssue",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create new
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
|
||||||
|
Add an existing issue
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ import { addSpaceIfCamelCase } from "constants/common";
|
|||||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// types
|
// types
|
||||||
import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types";
|
import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types";
|
||||||
import SingleIssue from "components/project/common/board-view/single-issue";
|
import SingleIssue from "components/common/board-view/single-issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedGroup: NestedKeyOf<IIssue> | null;
|
selectedGroup: NestedKeyOf<IIssue> | null;
|
||||||
|
@ -122,7 +122,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-x-2 p-2 bg-gray-50">
|
<div className="flex items-center gap-x-2 p-2 bg-gray-50">
|
||||||
<div className="w-8 h-8 shrink-0">
|
<div className="flex-shrink-0 h-8 w-8">
|
||||||
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
|
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
@ -150,7 +150,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute top-full z-50 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
|
<Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
|
||||||
<Controller
|
<Controller
|
||||||
name="color"
|
name="color"
|
||||||
control={control}
|
control={control}
|
||||||
|
@ -55,7 +55,7 @@ const ConfirmIssueDeletion: React.FC<Props> = ({ isOpen, handleClose, data }) =>
|
|||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
mutate(CYCLE_ISSUES(data.issue_cycle.id));
|
mutate(CYCLE_ISSUES(data.issue_cycle?.id ?? ""));
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
type: "success",
|
type: "success",
|
||||||
|
@ -29,7 +29,7 @@ import CreateUpdateStateModal from "components/project/issues/BoardView/state/cr
|
|||||||
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
|
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
|
||||||
// types
|
// types
|
||||||
import type { IIssue, IssueResponse } from "types";
|
import type { IIssue, IssueResponse } from "types";
|
||||||
// fetching keys
|
// fetch keys
|
||||||
import {
|
import {
|
||||||
PROJECT_ISSUES_DETAILS,
|
PROJECT_ISSUES_DETAILS,
|
||||||
PROJECT_ISSUES_LIST,
|
PROJECT_ISSUES_LIST,
|
||||||
|
@ -47,14 +47,14 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
<div className="p-1">
|
<div className="py-1">
|
||||||
{cycles?.map((cycle) => (
|
{cycles?.map((cycle) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={cycle.id}
|
key={cycle.id}
|
||||||
value={cycle.id}
|
value={cycle.id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`relative cursor-pointer select-none p-2 rounded-md ${
|
`text-gray-900 cursor-pointer select-none p-2 ${
|
||||||
active ? "bg-theme text-white" : "text-gray-900"
|
active ? "bg-indigo-50" : ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -99,14 +99,14 @@ const SelectLabels: React.FC<Props> = ({ control }) => {
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
<div className="p-1">
|
<div className="py-1">
|
||||||
{issueLabels?.map((label) => (
|
{issueLabels?.map((label) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={label.id}
|
key={label.id}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`${
|
`${
|
||||||
active ? "text-white bg-theme" : "text-gray-900"
|
active ? "bg-indigo-50" : ""
|
||||||
} flex items-center gap-2 cursor-pointer select-none w-full p-2 rounded-md`
|
} flex items-center gap-2 text-gray-900 cursor-pointer select-none w-full p-2`
|
||||||
}
|
}
|
||||||
value={label.id}
|
value={label.id}
|
||||||
>
|
>
|
||||||
|
@ -42,14 +42,14 @@ const SelectPriority: React.FC<Props> = ({ control }) => {
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 w-full w-[5rem] bg-white shadow-lg max-h-28 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-xs">
|
<Listbox.Options className="absolute z-10 mt-1 w-full w-[5rem] bg-white shadow-lg max-h-28 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-xs">
|
||||||
<div className="p-1">
|
<div className="py-1">
|
||||||
{PRIORITIES.map((priority) => (
|
{PRIORITIES.map((priority) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={priority}
|
key={priority}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`${
|
`${
|
||||||
active ? "text-white bg-theme" : "text-gray-900"
|
active ? "bg-indigo-50" : ""
|
||||||
} cursor-pointer select-none relative p-2 rounded-md`
|
} text-gray-900 cursor-pointer select-none p-2`
|
||||||
}
|
}
|
||||||
value={priority}
|
value={priority}
|
||||||
>
|
>
|
||||||
|
@ -28,7 +28,7 @@ const SelectState: React.FC<Props> = ({ control, setIsOpen }) => {
|
|||||||
<CustomListbox
|
<CustomListbox
|
||||||
title="State"
|
title="State"
|
||||||
options={states?.map((state) => {
|
options={states?.map((state) => {
|
||||||
return { value: state.id, display: state.name };
|
return { value: state.id, display: state.name, color: state.color };
|
||||||
})}
|
})}
|
||||||
value={value}
|
value={value}
|
||||||
optionsFontsize="sm"
|
optionsFontsize="sm"
|
||||||
|
@ -93,6 +93,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
console.log(res);
|
console.log(res);
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
issueLabelMutate((prevData) => [...(prevData ?? []), res], false);
|
issueLabelMutate((prevData) => [...(prevData ?? []), res], false);
|
||||||
|
submitChanges({ labels_list: [res.id] });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(issueDetail);
|
console.log(issueDetail?.labels, issueDetail?.labels_list);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -248,25 +249,32 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="basis-1/2">
|
<div className="basis-1/2">
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex gap-1 flex-wrap">
|
||||||
{issueDetail?.label_details.map((label) => (
|
{issueDetail?.labels.map((label) => {
|
||||||
<span
|
const singleLabel = issueLabels?.find((l) => l.id === label);
|
||||||
key={label.id}
|
|
||||||
className="group flex items-center gap-1 border rounded-2xl text-xs px-1 py-0.5 hover:bg-red-50 hover:border-red-500 cursor-pointer"
|
if (!singleLabel) return null;
|
||||||
onClick={() => {
|
|
||||||
const updatedLabels = issueDetail?.labels.filter((l) => l !== label.id);
|
return (
|
||||||
submitChanges({
|
|
||||||
labels_list: updatedLabels,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
className="h-2 w-2 rounded-full flex-shrink-0"
|
key={singleLabel.id}
|
||||||
style={{ backgroundColor: label.colour ?? "green" }}
|
className="group flex items-center gap-1 border rounded-2xl text-xs px-1 py-0.5 hover:bg-red-50 hover:border-red-500 cursor-pointer"
|
||||||
></span>
|
onClick={() => {
|
||||||
{label.name}
|
const updatedLabels = issueDetail?.labels.filter((l) => l !== label);
|
||||||
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />
|
// submitChanges({
|
||||||
</span>
|
// labels_list: updatedLabels,
|
||||||
))}
|
// });
|
||||||
|
console.log(updatedLabels);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="h-2 w-2 rounded-full flex-shrink-0"
|
||||||
|
style={{ backgroundColor: singleLabel.colour ?? "green" }}
|
||||||
|
></span>
|
||||||
|
{singleLabel.name}
|
||||||
|
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="labels_list"
|
name="labels_list"
|
||||||
@ -274,9 +282,9 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
<Listbox
|
<Listbox
|
||||||
as="div"
|
as="div"
|
||||||
value={value}
|
value={value}
|
||||||
multiple
|
|
||||||
onChange={(val: any) => submitChanges({ labels_list: val })}
|
onChange={(val: any) => submitChanges({ labels_list: val })}
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
|
multiple
|
||||||
>
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
|
@ -58,7 +58,7 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
|
|||||||
>
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Listbox.Button className="w-full flex justify-end items-center gap-1 text-xs cursor-pointer">
|
<Listbox.Button className="w-full flex items-center gap-1 text-xs cursor-pointer">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
value ? "" : "text-gray-900",
|
value ? "" : "text-gray-900",
|
||||||
@ -121,9 +121,12 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
|
|||||||
<Transition
|
<Transition
|
||||||
show={open}
|
show={open}
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
leave="transition ease-in duration-100"
|
enter="transition ease-out duration-100"
|
||||||
leaveFrom="opacity-100"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
leaveTo="opacity-0"
|
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 className="absolute z-10 right-0 mt-1 w-auto bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
<Listbox.Options className="absolute z-10 right-0 mt-1 w-auto bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
// react
|
||||||
|
import React from "react";
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { Control, Controller } from "react-hook-form";
|
import { Control, Controller } from "react-hook-form";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// headless ui
|
// ui
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Spinner, CustomSelect } from "ui";
|
||||||
|
// icons
|
||||||
|
import { ArrowPathIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
|
// common
|
||||||
import { classNames } from "constants/common";
|
import { classNames } from "constants/common";
|
||||||
import { Spinner } from "ui";
|
|
||||||
import React from "react";
|
|
||||||
import { ArrowPathIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
|
||||||
import CustomSelect from "ui/custom-select";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: Control<IIssue, any>;
|
control: Control<IIssue, any>;
|
||||||
@ -29,7 +30,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
|
|||||||
<div className="sm:basis-1/2">
|
<div className="sm:basis-1/2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="cycle"
|
name="issue_cycle"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<>
|
<>
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
@ -40,7 +41,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
|
|||||||
"hidden truncate sm:block text-left"
|
"hidden truncate sm:block text-left"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{value ? cycles?.find((c) => c.id === value)?.name : "None"}
|
{value ? cycles?.find((c) => c.id === value.cycle_detail.id)?.name : "None"}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
value={value}
|
value={value}
|
||||||
|
@ -155,11 +155,11 @@ const ListView: React.FC<Props> = ({
|
|||||||
{activeProject?.identifier}-{issue.sequence_id}
|
{activeProject?.identifier}-{issue.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="">{issue.name}</span>
|
<span>{issue.name}</span>
|
||||||
<div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
|
{/* <div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
|
||||||
<h5 className="font-medium mb-1">Name</h5>
|
<h5 className="font-medium mb-1">Name</h5>
|
||||||
<div>{issue.name}</div>
|
<div>{issue.name}</div>
|
||||||
</div>
|
</div> */}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@ import useUser from "lib/hooks/useUser";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Popover, Transition, Menu } from "@headlessui/react";
|
import { Popover, Transition, Menu } from "@headlessui/react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, Spinner } from "ui";
|
import { Button, CustomMenu, Input, Spinner } from "ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
@ -26,6 +26,7 @@ import {
|
|||||||
import { IIssueLabels } from "types";
|
import { IIssueLabels } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||||
|
import SingleLabel from "./single-label";
|
||||||
|
|
||||||
const defaultValues: Partial<IIssueLabels> = {
|
const defaultValues: Partial<IIssueLabels> = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -68,6 +69,14 @@ const LabelsSettings: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editLabel = (label: IIssueLabels) => {
|
||||||
|
setNewLabelForm(true);
|
||||||
|
setValue("colour", label.colour);
|
||||||
|
setValue("name", label.name);
|
||||||
|
setIsUpdating(true);
|
||||||
|
setLabelidForUpdate(label.id);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLabelUpdate: SubmitHandler<IIssueLabels> = (formData) => {
|
const handleLabelUpdate: SubmitHandler<IIssueLabels> = (formData) => {
|
||||||
if (!activeWorkspace || !activeProject || isSubmitting) return;
|
if (!activeWorkspace || !activeProject || isSubmitting) return;
|
||||||
issuesServices
|
issuesServices
|
||||||
@ -98,10 +107,6 @@ const LabelsSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLabelChildren = (labelId: string) => {
|
|
||||||
return issueLabels?.filter((l) => l.parent === labelId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="space-y-5">
|
<section className="space-y-5">
|
||||||
@ -110,31 +115,38 @@ const LabelsSettings: React.FC = () => {
|
|||||||
<h3 className="text-lg font-medium leading-6 text-gray-900">Labels</h3>
|
<h3 className="text-lg font-medium leading-6 text-gray-900">Labels</h3>
|
||||||
<p className="mt-1 text-sm text-gray-500">Manage the labels of this project.</p>
|
<p className="mt-1 text-sm text-gray-500">Manage the labels of this project.</p>
|
||||||
</div>
|
</div>
|
||||||
<Button className="flex items-center gap-x-1" onClick={() => setNewLabelForm(true)}>
|
<Button
|
||||||
|
theme="secondary"
|
||||||
|
className="flex items-center gap-x-1"
|
||||||
|
onClick={() => setNewLabelForm(true)}
|
||||||
|
>
|
||||||
<PlusIcon className="h-4 w-4" />
|
<PlusIcon className="h-4 w-4" />
|
||||||
New label
|
New label
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div
|
<div
|
||||||
className={`bg-white px-4 py-2 flex items-center gap-2 ${newLabelForm ? "" : "hidden"}`}
|
className={`md:w-2/3 border p-3 rounded-md flex items-center gap-2 ${
|
||||||
|
newLabelForm ? "" : "hidden"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="flex-shrink-0 h-8 w-8">
|
||||||
<Popover className="relative">
|
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`bg-white flex items-center gap-1 rounded-md p-1 outline-none focus:ring-2 focus:ring-indigo-500`}
|
className={`group inline-flex items-center text-base font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
|
||||||
|
open ? "text-gray-900" : "text-gray-500"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{watch("colour") && watch("colour") !== "" && (
|
{watch("colour") && watch("colour") !== "" && (
|
||||||
<span
|
<span
|
||||||
className="w-6 h-6 rounded"
|
className="w-4 h-4 rounded"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: watch("colour") ?? "green",
|
backgroundColor: watch("colour") ?? "green",
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
)}
|
)}
|
||||||
<ChevronDownIcon className="h-4 w-4" />
|
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
@ -146,7 +158,7 @@ const LabelsSettings: React.FC = () => {
|
|||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute z-20 transform left-0 mt-1 px-2 max-w-xs sm:px-0">
|
<Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
|
||||||
<Controller
|
<Controller
|
||||||
name="colour"
|
name="colour"
|
||||||
control={control}
|
control={control}
|
||||||
@ -193,89 +205,23 @@ const LabelsSettings: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{issueLabels ? (
|
<>
|
||||||
issueLabels.map((label) => {
|
{issueLabels ? (
|
||||||
const children = getLabelChildren(label.id);
|
issueLabels.map((label) => (
|
||||||
|
<SingleLabel
|
||||||
return (
|
key={label.id}
|
||||||
<React.Fragment key={label.id}>
|
label={label}
|
||||||
{children && children.length === 0 ? (
|
issueLabels={issueLabels}
|
||||||
<div className="bg-white p-2 flex items-center justify-between text-gray-900 rounded-md">
|
editLabel={editLabel}
|
||||||
<div className="flex items-center gap-2">
|
handleLabelDelete={handleLabelDelete}
|
||||||
<span
|
/>
|
||||||
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
))
|
||||||
style={{
|
) : (
|
||||||
backgroundColor: label.colour,
|
<div className="flex justify-center py-4">
|
||||||
}}
|
<Spinner />
|
||||||
/>
|
</div>
|
||||||
<p className="text-sm">{label.name}</p>
|
)}
|
||||||
</div>
|
</>
|
||||||
<div>
|
|
||||||
<Menu as="div" className="relative">
|
|
||||||
<Menu.Button
|
|
||||||
as="button"
|
|
||||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-100 duration-300 outline-none`}
|
|
||||||
>
|
|
||||||
<EllipsisHorizontalIcon className="h-4 w-4" />
|
|
||||||
</Menu.Button>
|
|
||||||
<Menu.Items className="absolute origin-top-right right-0.5 mt-1 p-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10">
|
|
||||||
<Menu.Item>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="text-left p-2 text-gray-900 hover:bg-theme hover:text-white rounded-md text-xs whitespace-nowrap w-full"
|
|
||||||
onClick={() => {
|
|
||||||
setNewLabelForm(true);
|
|
||||||
setValue("colour", label.colour);
|
|
||||||
setValue("name", label.name);
|
|
||||||
setIsUpdating(true);
|
|
||||||
setLabelidForUpdate(label.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
<div className="hover:bg-gray-100 border-b last:border-0">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="text-left p-2 text-gray-900 hover:bg-theme hover:text-white rounded-md text-xs whitespace-nowrap w-full"
|
|
||||||
onClick={() => handleLabelDelete(label.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Items>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white p-4 text-gray-900 rounded-md">
|
|
||||||
<h3 className="font-medium leading-5 flex items-center gap-2">
|
|
||||||
<RectangleGroupIcon className="h-5 w-5" />
|
|
||||||
This is the label group title
|
|
||||||
</h3>
|
|
||||||
<div className="pl-5 mt-4">
|
|
||||||
<div className="group text-sm flex justify-between items-center p-2 hover:bg-gray-100 rounded">
|
|
||||||
<h5 className="flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-red-600 rounded-full"></div>
|
|
||||||
This is the label title
|
|
||||||
</h5>
|
|
||||||
<button type="button" className="hidden group-hover:block">
|
|
||||||
<PencilIcon className="h-3 w-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className="flex justify-center py-4">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
160
apps/app/components/project/settings/labels/single-label.tsx
Normal file
160
apps/app/components/project/settings/labels/single-label.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// react
|
||||||
|
import React, { useState } from "react";
|
||||||
|
// react-hook-form
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
// headless ui
|
||||||
|
import { Popover, Transition } from "@headlessui/react";
|
||||||
|
// ui
|
||||||
|
import { Button, CustomMenu, Input } from "ui";
|
||||||
|
// icons
|
||||||
|
import { PencilIcon, RectangleGroupIcon } from "@heroicons/react/24/outline";
|
||||||
|
// types
|
||||||
|
import { IIssueLabels } from "types";
|
||||||
|
import { TwitterPicker } from "react-color";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: IIssueLabels;
|
||||||
|
issueLabels: IIssueLabels[];
|
||||||
|
editLabel: (label: IIssueLabels) => void;
|
||||||
|
handleLabelDelete: (labelId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValues: Partial<IIssueLabels> = {
|
||||||
|
name: "",
|
||||||
|
colour: "#ff0000",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SingleLabel: React.FC<Props> = ({ label, issueLabels, editLabel, handleLabelDelete }) => {
|
||||||
|
const [newLabelForm, setNewLabelForm] = useState(true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
setError,
|
||||||
|
watch,
|
||||||
|
reset,
|
||||||
|
control,
|
||||||
|
} = useForm<IIssueLabels>({ defaultValues });
|
||||||
|
|
||||||
|
const children = issueLabels?.filter((l) => l.parent === label.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children && children.length === 0 ? (
|
||||||
|
<div className="md:w-2/3 gap-2 border p-3 rounded-md divide-y space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="flex-shrink-0 h-3 w-3 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: label.colour,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<h6 className="text-sm">{label.name}</h6>
|
||||||
|
</div>
|
||||||
|
<CustomMenu ellipsis>
|
||||||
|
<CustomMenu.MenuItem>Convert to group</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
editLabel(label);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
|
||||||
|
Delete
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-2 ${newLabelForm ? "" : "hidden"}`}>
|
||||||
|
<div className="flex-shrink-0 h-8 w-8">
|
||||||
|
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Popover.Button
|
||||||
|
className={`group inline-flex items-center text-base font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
|
||||||
|
open ? "text-gray-900" : "text-gray-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{watch("colour") && watch("colour") !== "" && (
|
||||||
|
<span
|
||||||
|
className="w-4 h-4 rounded"
|
||||||
|
style={{
|
||||||
|
backgroundColor: watch("colour") ?? "green",
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
</Popover.Button>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={React.Fragment}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 translate-y-1"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-150"
|
||||||
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
|
leaveTo="opacity-0 translate-y-1"
|
||||||
|
>
|
||||||
|
<Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
|
||||||
|
<Controller
|
||||||
|
name="colour"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<TwitterPicker
|
||||||
|
color={value}
|
||||||
|
onChange={(value) => onChange(value.hex)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col justify-center">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="labelName"
|
||||||
|
name="name"
|
||||||
|
register={register}
|
||||||
|
placeholder="Lable title"
|
||||||
|
validations={{
|
||||||
|
required: "Label title is required",
|
||||||
|
}}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="button" theme="secondary" onClick={() => setNewLabelForm(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="button" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? "Adding" : "Add"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white p-4 text-gray-900 rounded-md">
|
||||||
|
<h3 className="font-medium leading-5 flex items-center gap-2">
|
||||||
|
<RectangleGroupIcon className="h-5 w-5" />
|
||||||
|
This is the label group title
|
||||||
|
</h3>
|
||||||
|
<div className="pl-5 mt-4">
|
||||||
|
<div className="group text-sm flex justify-between items-center p-2 hover:bg-gray-100 rounded">
|
||||||
|
<h5 className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 bg-red-600 rounded-full"></div>
|
||||||
|
This is the label title
|
||||||
|
</h5>
|
||||||
|
<button type="button" className="hidden group-hover:block">
|
||||||
|
<PencilIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SingleLabel;
|
@ -56,50 +56,7 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
|
|||||||
<span>Add</span>
|
<span>Add</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md:w-2/3 space-y-1 border p-1 rounded-xl bg-gray-50">
|
<div className="md:w-2/3 space-y-1 border p-1 rounded-xl">
|
||||||
<div className="w-full">
|
|
||||||
{groupedStates[key]?.map((state) =>
|
|
||||||
state.id !== selectedState ? (
|
|
||||||
<div
|
|
||||||
key={state.id}
|
|
||||||
className={`bg-gray-50 px-5 py-4 flex justify-between items-center border-b ${
|
|
||||||
Boolean(activeGroup !== key) ? "last:border-0" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-x-8">
|
|
||||||
<div
|
|
||||||
className="w-6 h-6 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: state.color,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<h4>{addSpaceIfCamelCase(state.name)}</h4>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<button type="button" onClick={() => setSelectDeleteState(state.id)}>
|
|
||||||
<TrashIcon className="h-5 w-5 text-red-400" />
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => setSelectedState(state.id)}>
|
|
||||||
<PencilSquareIcon className="h-5 w-5 text-gray-400" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={`border-b last:border-b-0`} key={state.id}>
|
|
||||||
<CreateUpdateStateInline
|
|
||||||
projectId={projectId as string}
|
|
||||||
onClose={() => {
|
|
||||||
setActiveGroup(null);
|
|
||||||
setSelectedState(null);
|
|
||||||
}}
|
|
||||||
workspaceSlug={activeWorkspace?.slug}
|
|
||||||
data={states?.find((state) => state.id === selectedState) ?? null}
|
|
||||||
selectedGroup={key as keyof StateGroup}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{key === activeGroup && (
|
{key === activeGroup && (
|
||||||
<CreateUpdateStateInline
|
<CreateUpdateStateInline
|
||||||
projectId={projectId as string}
|
projectId={projectId as string}
|
||||||
@ -112,6 +69,47 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
|
|||||||
selectedGroup={key as keyof StateGroup}
|
selectedGroup={key as keyof StateGroup}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{groupedStates[key]?.map((state) =>
|
||||||
|
state.id !== selectedState ? (
|
||||||
|
<div
|
||||||
|
key={state.id}
|
||||||
|
className={`bg-gray-50 p-3 flex justify-between items-center gap-2 border-b ${
|
||||||
|
Boolean(activeGroup !== key) ? "last:border-0" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div
|
||||||
|
className="flex-shrink-0 h-3 w-3 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: state.color,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<h6 className="text-sm">{addSpaceIfCamelCase(state.name)}</h6>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button type="button" onClick={() => setSelectDeleteState(state.id)}>
|
||||||
|
<TrashIcon className="h-4 w-4 text-red-400" />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => setSelectedState(state.id)}>
|
||||||
|
<PencilSquareIcon className="h-4 w-4 text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={`border-b last:border-b-0`} key={state.id}>
|
||||||
|
<CreateUpdateStateInline
|
||||||
|
projectId={projectId as string}
|
||||||
|
onClose={() => {
|
||||||
|
setActiveGroup(null);
|
||||||
|
setSelectedState(null);
|
||||||
|
}}
|
||||||
|
workspaceSlug={activeWorkspace?.slug}
|
||||||
|
data={states?.find((state) => state.id === selectedState) ?? null}
|
||||||
|
selectedGroup={key as keyof StateGroup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
@ -1,7 +1,6 @@
|
|||||||
// react
|
// react
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
// next
|
// next
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// swr
|
// swr
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
@ -12,6 +11,9 @@ import AppLayout from "layouts/app-layout";
|
|||||||
// components
|
// components
|
||||||
import CyclesListView from "components/project/cycles/list-view";
|
import CyclesListView from "components/project/cycles/list-view";
|
||||||
import CyclesBoardView from "components/project/cycles/board-view";
|
import CyclesBoardView from "components/project/cycles/board-view";
|
||||||
|
import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
|
||||||
|
import CycleIssuesListModal from "components/project/cycles/cycle-issues-list-modal";
|
||||||
|
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "lib/services/issues.service";
|
import issuesServices from "lib/services/issues.service";
|
||||||
import cycleServices from "lib/services/cycles.service";
|
import cycleServices from "lib/services/cycles.service";
|
||||||
@ -28,23 +30,11 @@ import { BreadcrumbItem, Breadcrumbs, CustomMenu } from "ui";
|
|||||||
import { Squares2X2Icon } from "@heroicons/react/20/solid";
|
import { Squares2X2Icon } from "@heroicons/react/20/solid";
|
||||||
import { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
|
import { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import {
|
import { CycleIssueResponse, IIssue, NestedKeyOf, Properties, SelectIssue } from "types";
|
||||||
CycleIssueResponse,
|
|
||||||
IIssue,
|
|
||||||
IssueResponse,
|
|
||||||
NestedKeyOf,
|
|
||||||
Properties,
|
|
||||||
SelectIssue,
|
|
||||||
SelectSprintType,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { CYCLE_ISSUES, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
// constants
|
// common
|
||||||
import { classNames, replaceUnderscoreIfSnakeCase } from "constants/common";
|
import { classNames, replaceUnderscoreIfSnakeCase } from "constants/common";
|
||||||
import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
|
|
||||||
import CycleIssuesListModal from "components/project/cycles/cycle-issues-list-modal";
|
|
||||||
import ConfirmCycleDeletion from "components/project/cycles/confirm-cycle-deletion";
|
|
||||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
|
||||||
|
|
||||||
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
||||||
{ name: "State", key: "state_detail.name" },
|
{ name: "State", key: "state_detail.name" },
|
||||||
@ -77,15 +67,16 @@ const filterIssueOptions: Array<{
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
type Props = {};
|
const SingleCycle: React.FC = () => {
|
||||||
|
|
||||||
const SingleCycle: React.FC<Props> = () => {
|
|
||||||
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
||||||
const [selectedCycle, setSelectedCycle] = useState<SelectSprintType>();
|
|
||||||
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
|
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
|
||||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||||
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const [preloadedData, setPreloadedData] = useState<
|
||||||
|
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
const { activeWorkspace, activeProject, cycles, issues } = useUser();
|
const { activeWorkspace, activeProject, cycles, issues } = useUser();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -149,15 +140,8 @@ const SingleCycle: React.FC<Props> = () => {
|
|||||||
issue?: IIssue,
|
issue?: IIssue,
|
||||||
actionType: "create" | "edit" | "delete" = "create"
|
actionType: "create" | "edit" | "delete" = "create"
|
||||||
) => {
|
) => {
|
||||||
const cycle = cycles?.find((cycle) => cycle.id === cycleId);
|
if (issue) setSelectedIssues({ ...issue, actionType });
|
||||||
if (cycle) {
|
setIsIssueModalOpen(true);
|
||||||
setSelectedCycle({
|
|
||||||
...cycle,
|
|
||||||
actionType: "create-issue",
|
|
||||||
});
|
|
||||||
if (issue) setSelectedIssues({ ...issue, actionType });
|
|
||||||
setIsIssueModalOpen(true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openIssuesListModal = () => {
|
const openIssuesListModal = () => {
|
||||||
@ -229,13 +213,9 @@ const SingleCycle: React.FC<Props> = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateIssuesModal
|
<CreateUpdateIssuesModal
|
||||||
isOpen={
|
isOpen={isIssueModalOpen && selectedIssues?.actionType !== "delete"}
|
||||||
isIssueModalOpen &&
|
|
||||||
selectedCycle?.actionType === "create-issue" &&
|
|
||||||
selectedIssues?.actionType !== "delete"
|
|
||||||
}
|
|
||||||
data={selectedIssues}
|
data={selectedIssues}
|
||||||
prePopulateData={{ sprints: selectedCycle?.id }}
|
prePopulateData={{ sprints: cycleId as string, ...preloadedData }}
|
||||||
setIsOpen={setIsIssueModalOpen}
|
setIsOpen={setIsIssueModalOpen}
|
||||||
projectId={activeProject?.id}
|
projectId={activeProject?.id}
|
||||||
/>
|
/>
|
||||||
@ -429,6 +409,7 @@ const SingleCycle: React.FC<Props> = () => {
|
|||||||
openCreateIssueModal={openCreateIssueModal}
|
openCreateIssueModal={openCreateIssueModal}
|
||||||
openIssuesListModal={openIssuesListModal}
|
openIssuesListModal={openIssuesListModal}
|
||||||
removeIssueFromCycle={removeIssueFromCycle}
|
removeIssueFromCycle={removeIssueFromCycle}
|
||||||
|
setPreloadedData={setPreloadedData}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-screen">
|
<div className="h-screen">
|
||||||
@ -442,6 +423,7 @@ const SingleCycle: React.FC<Props> = () => {
|
|||||||
openIssuesListModal={openIssuesListModal}
|
openIssuesListModal={openIssuesListModal}
|
||||||
handleDeleteIssue={setDeleteIssue}
|
handleDeleteIssue={setDeleteIssue}
|
||||||
partialUpdateIssue={partialUpdateIssue}
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
|
setPreloadedData={setPreloadedData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -106,7 +106,13 @@ const ProjectSprints: NextPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
Icon={PlusIcon}
|
Icon={PlusIcon}
|
||||||
action={() => setCreateUpdateCycleModal(true)}
|
action={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
ctrlKey: true,
|
||||||
|
key: "q",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,7 +94,7 @@ const IssueDetail: NextPage = () => {
|
|||||||
blockers_list: [],
|
blockers_list: [],
|
||||||
blocked_list: [],
|
blocked_list: [],
|
||||||
target_date: new Date().toString(),
|
target_date: new Date().toString(),
|
||||||
cycle: "",
|
issue_cycle: null,
|
||||||
labels_list: [],
|
labels_list: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -171,6 +171,7 @@ const IssueDetail: NextPage = () => {
|
|||||||
assignees_list:
|
assignees_list:
|
||||||
issueDetail.assignees_list ?? issueDetail.assignee_details?.map((user) => user.id),
|
issueDetail.assignees_list ?? issueDetail.assignee_details?.map((user) => user.id),
|
||||||
labels_list: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
|
labels_list: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
|
||||||
|
labels: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
|
||||||
});
|
});
|
||||||
}, [issueDetail, reset]);
|
}, [issueDetail, reset]);
|
||||||
|
|
||||||
@ -211,7 +212,7 @@ const IssueDetail: NextPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log(issueDetail);
|
console.log(issueDetail);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
|
@ -49,7 +49,7 @@ import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
|||||||
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
|
||||||
{ name: "State", key: "state_detail.name" },
|
{ name: "State", key: "state_detail.name" },
|
||||||
{ name: "Priority", key: "priority" },
|
{ name: "Priority", key: "priority" },
|
||||||
{ name: "Cycle", key: "issue_cycle.cycle_detail.name" },
|
// { name: "Cycle", key: "issue_cycle.cycle_detail.name" },
|
||||||
{ name: "Created By", key: "created_by" },
|
{ name: "Created By", key: "created_by" },
|
||||||
{ name: "None", key: null },
|
{ name: "None", key: null },
|
||||||
];
|
];
|
||||||
|
@ -80,13 +80,17 @@ const ProjectModules: NextPage = () => {
|
|||||||
title="Create a new module"
|
title="Create a new module"
|
||||||
description={
|
description={
|
||||||
<span>
|
<span>
|
||||||
Use <pre className="inline bg-gray-100 px-2 py-1 rounded">Ctrl/Command + Q</pre>{" "}
|
Use <pre className="inline bg-gray-100 px-2 py-1 rounded">Ctrl/Command + M</pre>{" "}
|
||||||
shortcut to create a new cycle
|
shortcut to create a new cycle
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
Icon={PlusIcon}
|
Icon={PlusIcon}
|
||||||
action={() => {
|
action={() => {
|
||||||
return;
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
ctrlKey: true,
|
||||||
|
key: "m",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</EmptySpace>
|
</EmptySpace>
|
||||||
|
@ -27,22 +27,22 @@ import { Breadcrumbs, BreadcrumbItem } from "ui/Breadcrumbs";
|
|||||||
// types
|
// types
|
||||||
import type { IProject, IWorkspace } from "types";
|
import type { IProject, IWorkspace } from "types";
|
||||||
|
|
||||||
const GeneralSettings = dynamic(() => import("components/project/settings/GeneralSettings"), {
|
const GeneralSettings = dynamic(() => import("components/project/settings/general"), {
|
||||||
loading: () => <p>Loading...</p>,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ControlSettings = dynamic(() => import("components/project/settings/ControlSettings"), {
|
const ControlSettings = dynamic(() => import("components/project/settings/control"), {
|
||||||
loading: () => <p>Loading...</p>,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const StatesSettings = dynamic(() => import("components/project/settings/StatesSettings"), {
|
const StatesSettings = dynamic(() => import("components/project/settings/states"), {
|
||||||
loading: () => <p>Loading...</p>,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const LabelsSettings = dynamic(() => import("components/project/settings/LabelsSettings"), {
|
const LabelsSettings = dynamic(() => import("components/project/settings/labels"), {
|
||||||
loading: () => <p>Loading...</p>,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
3
apps/app/types/issues.d.ts
vendored
3
apps/app/types/issues.d.ts
vendored
@ -30,7 +30,6 @@ export interface IIssue {
|
|||||||
label_details: any[];
|
label_details: any[];
|
||||||
assignee_details: IUser[];
|
assignee_details: IUser[];
|
||||||
assignees_list: string[];
|
assignees_list: string[];
|
||||||
bridge?: string;
|
|
||||||
blocked_by_issue_details: any[];
|
blocked_by_issue_details: any[];
|
||||||
blocked_issues: BlockeIssue[];
|
blocked_issues: BlockeIssue[];
|
||||||
blocker_issues: BlockeIssue[];
|
blocker_issues: BlockeIssue[];
|
||||||
@ -51,7 +50,7 @@ export interface IIssue {
|
|||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
};
|
} | null;
|
||||||
description: any;
|
description: any;
|
||||||
priority: string | null;
|
priority: string | null;
|
||||||
start_date: string | null;
|
start_date: string | null;
|
||||||
|
@ -39,7 +39,7 @@ const Button = React.forwardRef<HTMLButtonElement, Props>(
|
|||||||
disabled ? "opacity-70" : ""
|
disabled ? "opacity-70" : ""
|
||||||
} text-white shadow-sm bg-theme hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-transparent`
|
} text-white shadow-sm bg-theme hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-transparent`
|
||||||
: theme === "secondary"
|
: theme === "secondary"
|
||||||
? "border bg-white hover:bg-gray-100"
|
? "border bg-transparent hover:bg-gray-100"
|
||||||
: theme === "success"
|
: theme === "success"
|
||||||
? `${
|
? `${
|
||||||
disabled ? "opacity-70" : ""
|
disabled ? "opacity-70" : ""
|
||||||
|
@ -92,7 +92,7 @@ const CustomListbox: React.FC<Props> = ({
|
|||||||
: ""
|
: ""
|
||||||
} rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none z-10`}
|
} rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none z-10`}
|
||||||
>
|
>
|
||||||
<div className="p-1">
|
<div className="py-1">
|
||||||
{options ? (
|
{options ? (
|
||||||
options.length > 0 ? (
|
options.length > 0 ? (
|
||||||
options.map((option) => (
|
options.map((option) => (
|
||||||
@ -100,8 +100,8 @@ const CustomListbox: React.FC<Props> = ({
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`${
|
`${
|
||||||
active ? "text-white bg-theme" : "text-gray-900"
|
active ? "bg-indigo-50" : ""
|
||||||
} cursor-pointer select-none relative p-2 rounded-md`
|
} text-gray-900 cursor-pointer select-none relative p-2`
|
||||||
}
|
}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
>
|
>
|
||||||
@ -115,8 +115,16 @@ const CustomListbox: React.FC<Props> = ({
|
|||||||
: value === option.value)
|
: value === option.value)
|
||||||
? "font-semibold"
|
? "font-semibold"
|
||||||
: "font-normal"
|
: "font-normal"
|
||||||
} block truncate`}
|
} flex items-center gap-2 truncate`}
|
||||||
>
|
>
|
||||||
|
{option.color && (
|
||||||
|
<span
|
||||||
|
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: option.color,
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
{option.display}
|
{option.display}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
2
apps/app/ui/custom-listbox/types.d.ts
vendored
2
apps/app/ui/custom-listbox/types.d.ts
vendored
@ -1,7 +1,7 @@
|
|||||||
export type Props = {
|
export type Props = {
|
||||||
title?: string;
|
title?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
options?: Array<{ display: string; value: any }>;
|
options?: Array<{ display: string; value: any; color?: string }>;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
|
@ -15,11 +15,13 @@ const CustomMenu = ({
|
|||||||
label,
|
label,
|
||||||
className = "",
|
className = "",
|
||||||
ellipsis = false,
|
ellipsis = false,
|
||||||
width,
|
width = "auto",
|
||||||
textAlignment,
|
textAlignment,
|
||||||
|
withoutBorder = false,
|
||||||
|
optionsPosition = "right",
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className={`relative text-left ${className}`}>
|
<Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
|
||||||
<div>
|
<div>
|
||||||
{ellipsis ? (
|
{ellipsis ? (
|
||||||
<Menu.Button className="grid relative place-items-center hover:bg-gray-100 rounded p-1 focus:outline-none">
|
<Menu.Button className="grid relative place-items-center hover:bg-gray-100 rounded p-1 focus:outline-none">
|
||||||
@ -27,16 +29,20 @@ const CustomMenu = ({
|
|||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
) : (
|
) : (
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
className={`flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 w-full py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
|
className={`flex justify-between items-center gap-1 hover:bg-gray-100 px-2 py-1 cursor-pointer text-xs duration-300 ${
|
||||||
textAlignment === "right"
|
textAlignment === "right"
|
||||||
? "text-right"
|
? "text-right"
|
||||||
: textAlignment === "center"
|
: textAlignment === "center"
|
||||||
? "text-center"
|
? "text-center"
|
||||||
: "text-left"
|
: "text-left"
|
||||||
|
} ${
|
||||||
|
withoutBorder
|
||||||
|
? "rounded"
|
||||||
|
: "w-full border shadow-sm rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
{!withoutBorder && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -51,9 +57,9 @@ const CustomMenu = ({
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items
|
<Menu.Items
|
||||||
className={`absolute right-0 z-10 mt-1 origin-top-right rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
|
className={`absolute z-20 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
|
||||||
width === "auto" ? "min-w-full whitespace-nowrap" : "w-56"
|
optionsPosition === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||||
}`}
|
} ${width === "auto" ? "min-w-full whitespace-nowrap" : "w-56"}`}
|
||||||
>
|
>
|
||||||
<div className="py-1">{children}</div>
|
<div className="py-1">{children}</div>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
|
2
apps/app/ui/custom-menu/types.d.ts
vendored
2
apps/app/ui/custom-menu/types.d.ts
vendored
@ -5,6 +5,8 @@ export type Props = {
|
|||||||
ellipsis?: boolean;
|
ellipsis?: boolean;
|
||||||
width?: "auto";
|
width?: "auto";
|
||||||
textAlignment?: "left" | "center" | "right";
|
textAlignment?: "left" | "center" | "right";
|
||||||
|
withoutBorder?: boolean;
|
||||||
|
optionsPosition?: "left" | "right";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuItemProps = {
|
export type MenuItemProps = {
|
||||||
|
@ -4,6 +4,7 @@ export { default as Select } from "./Select";
|
|||||||
export { default as TextArea } from "./TextArea";
|
export { default as TextArea } from "./TextArea";
|
||||||
export { default as CustomListbox } from "./custom-listbox";
|
export { default as CustomListbox } from "./custom-listbox";
|
||||||
export { default as CustomMenu } from "./custom-menu";
|
export { default as CustomMenu } from "./custom-menu";
|
||||||
|
export { default as CustomSelect } from "./custom-select";
|
||||||
export { default as Spinner } from "./Spinner";
|
export { default as Spinner } from "./Spinner";
|
||||||
export { default as Tooltip } from "./Tooltip";
|
export { default as Tooltip } from "./Tooltip";
|
||||||
export { default as SearchListbox } from "./search-listbox";
|
export { default as SearchListbox } from "./search-listbox";
|
||||||
|
Loading…
Reference in New Issue
Block a user