fix: bugs fixed

This commit is contained in:
Aaryan Khandelwal 2022-12-19 20:30:09 +05:30
parent c4079c4e0c
commit a2db04f9ff
35 changed files with 719 additions and 569 deletions

View File

@ -0,0 +1,5 @@
const SingleBoard = () => {
return <></>;
};
export default SingleBoard;

View File

@ -117,7 +117,7 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100"
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) => (
<Listbox.Option
key={priority}
@ -186,18 +186,24 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100"
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) => (
<Listbox.Option
key={state.id}
className={({ active }) =>
classNames(
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}
>
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: state.color,
}}
></span>
{addSpaceIfCamelCase(state.name)}
</Listbox.Option>
))}
@ -316,7 +322,7 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100"
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) => (
<Listbox.Option
key={person.id}

View File

@ -18,6 +18,14 @@ type Props = {
removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
};
const CyclesBoardView: React.FC<Props> = ({
@ -30,6 +38,7 @@ const CyclesBoardView: React.FC<Props> = ({
removeIssueFromCycle,
partialUpdateIssue,
handleDeleteIssue,
setPreloadedData,
}) => {
const { states } = useUser();
@ -63,6 +72,12 @@ const CyclesBoardView: React.FC<Props> = ({
openCreateIssueModal={openCreateIssueModal}
partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue}
setPreloadedData={setPreloadedData}
stateId={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.id ?? null
: null
}
/>
))}
</div>

View File

@ -7,7 +7,7 @@ import workspaceService from "lib/services/workspace.service";
// hooks
import useUser from "lib/hooks/useUser";
// components
import SingleIssue from "components/project/common/board-view/single-issue";
import SingleIssue from "components/common/board-view/single-issue";
// headless ui
import { Menu, Transition } from "@headlessui/react";
// ui
@ -35,6 +35,15 @@ type Props = {
removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
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> = ({
@ -49,6 +58,8 @@ const SingleCycleBoard: React.FC<Props> = ({
removeIssueFromCycle,
partialUpdateIssue,
handleDeleteIssue,
setPreloadedData,
stateId,
}) => {
// Collapse/Expand
const [show, setState] = useState(true);
@ -109,7 +120,18 @@ const SingleCycleBoard: React.FC<Props> = ({
</div>
<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
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
@ -150,55 +172,35 @@ const SingleCycleBoard: React.FC<Props> = ({
);
})}
<Menu as="div" className="relative text-left">
<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"
<CustomMenu
label={
<span className="flex items-center gap-1">
<PlusIcon className="h-3 w-3" />
Add issue
</span>
}
className="mt-1"
optionsPosition="left"
withoutBorder
>
<CustomMenu.MenuItem
onClick={() => {
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">
<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>
</Menu.Items>
</Transition>
</Menu>
Create new
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
Add an existing issue
</CustomMenu.MenuItem>
</CustomMenu>
</div>
</div>
</div>

View File

@ -28,13 +28,21 @@ import workspaceService from "lib/services/workspace.service";
type Props = {
groupedByIssues: {
[key: string]: IIssue[];
[key: string]: (IIssue & { bridge?: string })[];
};
properties: Properties;
selectedGroup: NestedKeyOf<IIssue> | null;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void;
removeIssueFromCycle: (bridgeId: string) => void;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
};
const CyclesListView: React.FC<Props> = ({
@ -44,8 +52,9 @@ const CyclesListView: React.FC<Props> = ({
openIssuesListModal,
properties,
removeIssueFromCycle,
setPreloadedData,
}) => {
const { activeWorkspace, activeProject } = useUser();
const { activeWorkspace, activeProject, states } = useUser();
const { data: people } = useSWR<IWorkspaceMember[]>(
activeWorkspace ? WORKSPACE_MEMBERS : null,
@ -54,273 +63,261 @@ const CyclesListView: React.FC<Props> = ({
return (
<div className="flex flex-col space-y-5">
{Object.keys(groupedByIssues).map((singleGroup) => (
<Disclosure key={singleGroup} as="div" defaultOpen>
{({ open }) => (
<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">
<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;
{Object.keys(groupedByIssues).map((singleGroup) => {
const stateId =
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.id ?? null
: null;
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 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>
);
})
return (
<Disclosure key={singleGroup} as="div" defaultOpen>
{({ open }) => (
<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">
<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>
) : (
<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>
<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>
<h2 className="font-medium leading-5">All Issues</h2>
)}
<p className="text-gray-500 text-sm">
{groupedByIssues[singleGroup as keyof IIssue].length}
</p>
</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>
</Menu>
</div>
)}
</Disclosure>
))}
<div className="p-3">
<CustomMenu
label={
<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>
);
};

View File

@ -20,7 +20,7 @@ import { addSpaceIfCamelCase } from "constants/common";
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
// 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 = {
selectedGroup: NestedKeyOf<IIssue> | null;

View File

@ -122,7 +122,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
return (
<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">
{({ open }) => (
<>
@ -150,7 +150,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0"
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
name="color"
control={control}

View File

@ -55,7 +55,7 @@ const ConfirmIssueDeletion: React.FC<Props> = ({ isOpen, handleClose, data }) =>
},
false
);
mutate(CYCLE_ISSUES(data.issue_cycle.id));
mutate(CYCLE_ISSUES(data.issue_cycle?.id ?? ""));
setToastAlert({
title: "Success",
type: "success",

View File

@ -29,7 +29,7 @@ import CreateUpdateStateModal from "components/project/issues/BoardView/state/cr
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
// types
import type { IIssue, IssueResponse } from "types";
// fetching keys
// fetch keys
import {
PROJECT_ISSUES_DETAILS,
PROJECT_ISSUES_LIST,

View File

@ -47,14 +47,14 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
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">
<div className="p-1">
<div className="py-1">
{cycles?.map((cycle) => (
<Listbox.Option
key={cycle.id}
value={cycle.id}
className={({ active }) =>
`relative cursor-pointer select-none p-2 rounded-md ${
active ? "bg-theme text-white" : "text-gray-900"
`text-gray-900 cursor-pointer select-none p-2 ${
active ? "bg-indigo-50" : ""
}`
}
>

View File

@ -99,14 +99,14 @@ const SelectLabels: React.FC<Props> = ({ control }) => {
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">
<div className="p-1">
<div className="py-1">
{issueLabels?.map((label) => (
<Listbox.Option
key={label.id}
className={({ active }) =>
`${
active ? "text-white bg-theme" : "text-gray-900"
} flex items-center gap-2 cursor-pointer select-none w-full p-2 rounded-md`
active ? "bg-indigo-50" : ""
} flex items-center gap-2 text-gray-900 cursor-pointer select-none w-full p-2`
}
value={label.id}
>

View File

@ -42,14 +42,14 @@ const SelectPriority: React.FC<Props> = ({ control }) => {
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">
<div className="p-1">
<div className="py-1">
{PRIORITIES.map((priority) => (
<Listbox.Option
key={priority}
className={({ active }) =>
`${
active ? "text-white bg-theme" : "text-gray-900"
} cursor-pointer select-none relative p-2 rounded-md`
active ? "bg-indigo-50" : ""
} text-gray-900 cursor-pointer select-none p-2`
}
value={priority}
>

View File

@ -28,7 +28,7 @@ const SelectState: React.FC<Props> = ({ control, setIsOpen }) => {
<CustomListbox
title="State"
options={states?.map((state) => {
return { value: state.id, display: state.name };
return { value: state.id, display: state.name, color: state.color };
})}
value={value}
optionsFontsize="sm"

View File

@ -93,6 +93,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
console.log(res);
reset(defaultValues);
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 (
<>
@ -248,25 +249,32 @@ const IssueDetailSidebar: React.FC<Props> = ({
</div>
<div className="basis-1/2">
<div className="flex gap-1 flex-wrap">
{issueDetail?.label_details.map((label) => (
<span
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"
onClick={() => {
const updatedLabels = issueDetail?.labels.filter((l) => l !== label.id);
submitChanges({
labels_list: updatedLabels,
});
}}
>
{issueDetail?.labels.map((label) => {
const singleLabel = issueLabels?.find((l) => l.id === label);
if (!singleLabel) return null;
return (
<span
className="h-2 w-2 rounded-full flex-shrink-0"
style={{ backgroundColor: label.colour ?? "green" }}
></span>
{label.name}
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />
</span>
))}
key={singleLabel.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"
onClick={() => {
const updatedLabels = issueDetail?.labels.filter((l) => l !== label);
// submitChanges({
// 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
control={control}
name="labels_list"
@ -274,9 +282,9 @@ const IssueDetailSidebar: React.FC<Props> = ({
<Listbox
as="div"
value={value}
multiple
onChange={(val: any) => submitChanges({ labels_list: val })}
className="flex-shrink-0"
multiple
>
{({ open }) => (
<>

View File

@ -58,7 +58,7 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
>
{({ open }) => (
<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
className={classNames(
value ? "" : "text-gray-900",
@ -121,9 +121,12 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Listbox.Options 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">

View File

@ -1,16 +1,17 @@
// react
import React from "react";
// react-hook-form
import { Control, Controller } from "react-hook-form";
// hooks
import useUser from "lib/hooks/useUser";
// headless ui
import { Listbox, Transition } from "@headlessui/react";
// ui
import { Spinner, CustomSelect } from "ui";
// icons
import { ArrowPathIcon } from "@heroicons/react/24/outline";
// types
import { IIssue } from "types";
// 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 = {
control: Control<IIssue, any>;
@ -29,7 +30,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
<div className="sm:basis-1/2">
<Controller
control={control}
name="cycle"
name="issue_cycle"
render={({ field: { value } }) => (
<>
<CustomSelect
@ -40,7 +41,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
"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>
}
value={value}

View File

@ -155,11 +155,11 @@ const ListView: React.FC<Props> = ({
{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">
<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>
</div> */}
</a>
</Link>
</div>

View File

@ -13,7 +13,7 @@ import useUser from "lib/hooks/useUser";
// headless ui
import { Popover, Transition, Menu } from "@headlessui/react";
// ui
import { Button, Input, Spinner } from "ui";
import { Button, CustomMenu, Input, Spinner } from "ui";
// icons
import {
ChevronDownIcon,
@ -26,6 +26,7 @@ import {
import { IIssueLabels } from "types";
// fetch-keys
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
import SingleLabel from "./single-label";
const defaultValues: Partial<IIssueLabels> = {
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) => {
if (!activeWorkspace || !activeProject || isSubmitting) return;
issuesServices
@ -98,10 +107,6 @@ const LabelsSettings: React.FC = () => {
}
};
const getLabelChildren = (labelId: string) => {
return issueLabels?.filter((l) => l.parent === labelId);
};
return (
<>
<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>
<p className="mt-1 text-sm text-gray-500">Manage the labels of this project.</p>
</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" />
New label
</Button>
</div>
<div className="space-y-5">
<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>
<Popover className="relative">
<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={`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") !== "" && (
<span
className="w-6 h-6 rounded"
className="w-4 h-4 rounded"
style={{
backgroundColor: watch("colour") ?? "green",
}}
></span>
)}
<ChevronDownIcon className="h-4 w-4" />
</Popover.Button>
<Transition
@ -146,7 +158,7 @@ const LabelsSettings: React.FC = () => {
leaveFrom="opacity-100 translate-y-0"
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
name="colour"
control={control}
@ -193,89 +205,23 @@ const LabelsSettings: React.FC = () => {
</Button>
)}
</div>
{issueLabels ? (
issueLabels.map((label) => {
const children = getLabelChildren(label.id);
return (
<React.Fragment key={label.id}>
{children && children.length === 0 ? (
<div className="bg-white p-2 flex items-center justify-between text-gray-900 rounded-md">
<div className="flex items-center gap-2">
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: label.colour,
}}
/>
<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>
)}
<>
{issueLabels ? (
issueLabels.map((label) => (
<SingleLabel
key={label.id}
label={label}
issueLabels={issueLabels}
editLabel={editLabel}
handleLabelDelete={handleLabelDelete}
/>
))
) : (
<div className="flex justify-center py-4">
<Spinner />
</div>
)}
</>
</div>
</section>
</>

View 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;

View File

@ -56,50 +56,7 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
<span>Add</span>
</button>
</div>
<div className="w-full md:w-2/3 space-y-1 border p-1 rounded-xl bg-gray-50">
<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>
<div className="md:w-2/3 space-y-1 border p-1 rounded-xl">
{key === activeGroup && (
<CreateUpdateStateInline
projectId={projectId as string}
@ -112,6 +69,47 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
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>
</React.Fragment>
))}

View File

@ -1,7 +1,6 @@
// react
import React, { useState } from "react";
// next
import Link from "next/link";
import { useRouter } from "next/router";
// swr
import useSWR, { mutate } from "swr";
@ -12,6 +11,9 @@ import AppLayout from "layouts/app-layout";
// components
import CyclesListView from "components/project/cycles/list-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
import issuesServices from "lib/services/issues.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 { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
// types
import {
CycleIssueResponse,
IIssue,
IssueResponse,
NestedKeyOf,
Properties,
SelectIssue,
SelectSprintType,
} from "types";
import { CycleIssueResponse, IIssue, NestedKeyOf, Properties, SelectIssue } from "types";
// fetch-keys
import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys";
// constants
import { CYCLE_ISSUES, PROJECT_MEMBERS } from "constants/fetch-keys";
// 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 }> = [
{ name: "State", key: "state_detail.name" },
@ -77,15 +67,16 @@ const filterIssueOptions: Array<{
},
];
type Props = {};
const SingleCycle: React.FC<Props> = () => {
const SingleCycle: React.FC = () => {
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [selectedCycle, setSelectedCycle] = useState<SelectSprintType>();
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
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 router = useRouter();
@ -149,15 +140,8 @@ const SingleCycle: React.FC<Props> = () => {
issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create"
) => {
const cycle = cycles?.find((cycle) => cycle.id === cycleId);
if (cycle) {
setSelectedCycle({
...cycle,
actionType: "create-issue",
});
if (issue) setSelectedIssues({ ...issue, actionType });
setIsIssueModalOpen(true);
}
if (issue) setSelectedIssues({ ...issue, actionType });
setIsIssueModalOpen(true);
};
const openIssuesListModal = () => {
@ -229,13 +213,9 @@ const SingleCycle: React.FC<Props> = () => {
return (
<>
<CreateUpdateIssuesModal
isOpen={
isIssueModalOpen &&
selectedCycle?.actionType === "create-issue" &&
selectedIssues?.actionType !== "delete"
}
isOpen={isIssueModalOpen && selectedIssues?.actionType !== "delete"}
data={selectedIssues}
prePopulateData={{ sprints: selectedCycle?.id }}
prePopulateData={{ sprints: cycleId as string, ...preloadedData }}
setIsOpen={setIsIssueModalOpen}
projectId={activeProject?.id}
/>
@ -429,6 +409,7 @@ const SingleCycle: React.FC<Props> = () => {
openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal}
removeIssueFromCycle={removeIssueFromCycle}
setPreloadedData={setPreloadedData}
/>
) : (
<div className="h-screen">
@ -442,6 +423,7 @@ const SingleCycle: React.FC<Props> = () => {
openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData}
/>
</div>
)}

View File

@ -106,7 +106,13 @@ const ProjectSprints: NextPage = () => {
</span>
}
Icon={PlusIcon}
action={() => setCreateUpdateCycleModal(true)}
action={() => {
const e = new KeyboardEvent("keydown", {
ctrlKey: true,
key: "q",
});
document.dispatchEvent(e);
}}
/>
</EmptySpace>
</div>

View File

@ -94,7 +94,7 @@ const IssueDetail: NextPage = () => {
blockers_list: [],
blocked_list: [],
target_date: new Date().toString(),
cycle: "",
issue_cycle: null,
labels_list: [],
},
});
@ -171,6 +171,7 @@ const IssueDetail: NextPage = () => {
assignees_list:
issueDetail.assignees_list ?? issueDetail.assignee_details?.map((user) => user.id),
labels_list: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
labels: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
});
}, [issueDetail, reset]);
@ -211,7 +212,7 @@ const IssueDetail: NextPage = () => {
}
};
// console.log(issueDetail);
console.log(issueDetail);
return (
<AppLayout

View File

@ -49,7 +49,7 @@ import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
{ name: "State", key: "state_detail.name" },
{ 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: "None", key: null },
];

View File

@ -80,13 +80,17 @@ const ProjectModules: NextPage = () => {
title="Create a new module"
description={
<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
</span>
}
Icon={PlusIcon}
action={() => {
return;
const e = new KeyboardEvent("keydown", {
ctrlKey: true,
key: "m",
});
document.dispatchEvent(e);
}}
/>
</EmptySpace>

View File

@ -27,22 +27,22 @@ import { Breadcrumbs, BreadcrumbItem } from "ui/Breadcrumbs";
// 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>,
ssr: false,
});
const ControlSettings = dynamic(() => import("components/project/settings/ControlSettings"), {
const ControlSettings = dynamic(() => import("components/project/settings/control"), {
loading: () => <p>Loading...</p>,
ssr: false,
});
const StatesSettings = dynamic(() => import("components/project/settings/StatesSettings"), {
const StatesSettings = dynamic(() => import("components/project/settings/states"), {
loading: () => <p>Loading...</p>,
ssr: false,
});
const LabelsSettings = dynamic(() => import("components/project/settings/LabelsSettings"), {
const LabelsSettings = dynamic(() => import("components/project/settings/labels"), {
loading: () => <p>Loading...</p>,
ssr: false,
});

View File

@ -30,7 +30,6 @@ export interface IIssue {
label_details: any[];
assignee_details: IUser[];
assignees_list: string[];
bridge?: string;
blocked_by_issue_details: any[];
blocked_issues: BlockeIssue[];
blocker_issues: BlockeIssue[];
@ -51,7 +50,7 @@ export interface IIssue {
updated_at: Date;
updated_by: string;
workspace: string;
};
} | null;
description: any;
priority: string | null;
start_date: string | null;

View File

@ -39,7 +39,7 @@ const Button = React.forwardRef<HTMLButtonElement, Props>(
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`
: theme === "secondary"
? "border bg-white hover:bg-gray-100"
? "border bg-transparent hover:bg-gray-100"
: theme === "success"
? `${
disabled ? "opacity-70" : ""

View File

@ -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`}
>
<div className="p-1">
<div className="py-1">
{options ? (
options.length > 0 ? (
options.map((option) => (
@ -100,8 +100,8 @@ const CustomListbox: React.FC<Props> = ({
key={option.value}
className={({ active }) =>
`${
active ? "text-white bg-theme" : "text-gray-900"
} cursor-pointer select-none relative p-2 rounded-md`
active ? "bg-indigo-50" : ""
} text-gray-900 cursor-pointer select-none relative p-2`
}
value={option.value}
>
@ -115,8 +115,16 @@ const CustomListbox: React.FC<Props> = ({
: value === option.value)
? "font-semibold"
: "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}
</span>

View File

@ -1,7 +1,7 @@
export type Props = {
title?: string;
label?: string;
options?: Array<{ display: string; value: any }>;
options?: Array<{ display: string; value: any; color?: string }>;
icon?: JSX.Element;
value: any;
onChange: (value: any) => void;

View File

@ -15,11 +15,13 @@ const CustomMenu = ({
label,
className = "",
ellipsis = false,
width,
width = "auto",
textAlignment,
withoutBorder = false,
optionsPosition = "right",
}: Props) => {
return (
<Menu as="div" className={`relative text-left ${className}`}>
<Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
<div>
{ellipsis ? (
<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
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"
? "text-right"
: textAlignment === "center"
? "text-center"
: "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}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
{!withoutBorder && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Menu.Button>
)}
</div>
@ -51,9 +57,9 @@ const CustomMenu = ({
leaveTo="transform opacity-0 scale-95"
>
<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 ${
width === "auto" ? "min-w-full whitespace-nowrap" : "w-56"
}`}
className={`absolute z-20 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
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>
</Menu.Items>

View File

@ -5,6 +5,8 @@ export type Props = {
ellipsis?: boolean;
width?: "auto";
textAlignment?: "left" | "center" | "right";
withoutBorder?: boolean;
optionsPosition?: "left" | "right";
};
export type MenuItemProps = {

View File

@ -4,6 +4,7 @@ export { default as Select } from "./Select";
export { default as TextArea } from "./TextArea";
export { default as CustomListbox } from "./custom-listbox";
export { default as CustomMenu } from "./custom-menu";
export { default as CustomSelect } from "./custom-select";
export { default as Spinner } from "./Spinner";
export { default as Tooltip } from "./Tooltip";
export { default as SearchListbox } from "./search-listbox";