mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: blocked issues, style: issue details sidebar
This commit is contained in:
parent
afcf1083ff
commit
8ae9c3f15a
@ -28,7 +28,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type FormInput = {
|
type FormInput = {
|
||||||
issue: string[];
|
issues: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const CycleIssuesListModal: React.FC<Props> = ({
|
const CycleIssuesListModal: React.FC<Props> = ({
|
||||||
@ -56,12 +56,12 @@ const CycleIssuesListModal: React.FC<Props> = ({
|
|||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm<FormInput>({
|
} = useForm<FormInput>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
issue: [],
|
issues: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAddToCycle: SubmitHandler<FormInput> = (data) => {
|
const handleAddToCycle: SubmitHandler<FormInput> = (data) => {
|
||||||
if (!data.issue || data.issue.length === 0) {
|
if (!data.issues || data.issues.length === 0) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -120,7 +120,7 @@ const CycleIssuesListModal: React.FC<Props> = ({
|
|||||||
<form>
|
<form>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="issue"
|
name="issues"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Combobox as="div" {...field} multiple>
|
<Combobox as="div" {...field} multiple>
|
||||||
<div className="relative m-1">
|
<div className="relative m-1">
|
||||||
|
@ -63,7 +63,7 @@ const SingleStat: React.FC<Props> = ({ cycle, handleEditCycle, handleDeleteCycle
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-white p-3">
|
<div className="border bg-white p-3 rounded-md">
|
||||||
<div className="grid grid-cols-8 gap-2 divide-x">
|
<div className="grid grid-cols-8 gap-2 divide-x">
|
||||||
<div className="col-span-3 space-y-3">
|
<div className="col-span-3 space-y-3">
|
||||||
<div className="flex justify-between items-center gap-2">
|
<div className="flex justify-between items-center gap-2">
|
||||||
@ -74,7 +74,15 @@ const SingleStat: React.FC<Props> = ({ cycle, handleEditCycle, handleDeleteCycle
|
|||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs bg-gray-100 px-2 py-1 rounded-xl">
|
<span
|
||||||
|
className={`text-xs border px-3 py-0.5 rounded-xl ${
|
||||||
|
today < startDate
|
||||||
|
? "text-orange-500 border-orange-500"
|
||||||
|
: today > endDate
|
||||||
|
? "text-red-500 border-red-500"
|
||||||
|
: "text-green-500 border-green-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{today < startDate ? "Not started" : today > endDate ? "Over" : "Active"}
|
{today < startDate ? "Not started" : today > endDate ? "Over" : "Active"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -86,20 +94,19 @@ const SingleStat: React.FC<Props> = ({ cycle, handleEditCycle, handleDeleteCycle
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-x-2 gap-y-3 text-xs">
|
||||||
<div className="grid grid-cols-2 gap-x-2 gap-y-3 text-xs">
|
|
||||||
<div className="flex items-center gap-2 text-gray-500">
|
<div className="flex items-center gap-2 text-gray-500">
|
||||||
<CalendarDaysIcon className="h-4 w-4" />
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
Cycle dates
|
Cycle dates
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="col-span-2">
|
||||||
{renderShortNumericDateFormat(startDate)} - {renderShortNumericDateFormat(endDate)}
|
{renderShortNumericDateFormat(startDate)} - {renderShortNumericDateFormat(endDate)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-gray-500">
|
<div className="flex items-center gap-2 text-gray-500">
|
||||||
<UserIcon className="h-4 w-4" />
|
<UserIcon className="h-4 w-4" />
|
||||||
Created by
|
Created by
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="col-span-2 flex items-center gap-2">
|
||||||
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? (
|
{cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? (
|
||||||
<Image
|
<Image
|
||||||
src={cycle.owned_by.avatar}
|
src={cycle.owned_by.avatar}
|
||||||
@ -119,7 +126,7 @@ const SingleStat: React.FC<Props> = ({ cycle, handleEditCycle, handleDeleteCycle
|
|||||||
<CalendarDaysIcon className="h-4 w-4" />
|
<CalendarDaysIcon className="h-4 w-4" />
|
||||||
Active members
|
Active members
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div className="col-span-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button theme="secondary" className="flex items-center gap-2" disabled>
|
<Button theme="secondary" className="flex items-center gap-2" disabled>
|
||||||
|
@ -118,7 +118,7 @@ const CreateUpdateIssuesModal: React.FC<Props> = ({
|
|||||||
if (!activeWorkspace || !activeProject) return;
|
if (!activeWorkspace || !activeProject) return;
|
||||||
await issuesServices
|
await issuesServices
|
||||||
.addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleId, {
|
.addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleId, {
|
||||||
issue: [issueId],
|
issues: [issueId],
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate(CYCLE_ISSUES(cycleId));
|
mutate(CYCLE_ISSUES(cycleId));
|
||||||
|
@ -101,7 +101,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
if (activeWorkspace && activeProject && issueDetail)
|
if (activeWorkspace && activeProject && issueDetail)
|
||||||
issuesServices
|
issuesServices
|
||||||
.addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleId, {
|
.addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleId, {
|
||||||
issue: [issueDetail.id],
|
issues: [issueDetail.id],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
submitChanges({});
|
submitChanges({});
|
||||||
@ -211,7 +211,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
/>
|
/>
|
||||||
<SelectBlocked
|
<SelectBlocked
|
||||||
submitChanges={submitChanges}
|
issueDetail={issueDetail}
|
||||||
issuesList={issues?.results.filter((i) => i.id !== issueDetail?.id) ?? []}
|
issuesList={issues?.results.filter((i) => i.id !== issueDetail?.id) ?? []}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
/>
|
/>
|
||||||
@ -227,6 +227,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
|||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
|
id="issueDate"
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
onChange={(e: any) => {
|
onChange={(e: any) => {
|
||||||
submitChanges({ target_date: e.target.value });
|
submitChanges({ target_date: e.target.value });
|
||||||
|
@ -128,7 +128,7 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
|
|||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
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-48 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">
|
||||||
{people ? (
|
{people ? (
|
||||||
people.length > 0 ? (
|
people.length > 0 ? (
|
||||||
|
@ -10,32 +10,28 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
// ui
|
// ui
|
||||||
import { Button } from "ui";
|
import { Button } from "ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { FolderIcon, MagnifyingGlassIcon, FlagIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
FolderIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue, IssueResponse } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { classNames } from "constants/common";
|
import { classNames } from "constants/common";
|
||||||
|
import issuesService from "lib/services/issues.service";
|
||||||
|
|
||||||
type FormInput = {
|
type FormInput = {
|
||||||
issue_ids: string[];
|
issue_ids: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
issueDetail: IIssue | undefined;
|
||||||
issuesList: IIssue[];
|
issuesList: IIssue[];
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) => {
|
const SelectBlocked: React.FC<Props> = ({ issueDetail, issuesList, watch }) => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
||||||
|
|
||||||
const { activeProject, issues } = useUser();
|
const { activeWorkspace, activeProject, issues, mutateIssues } = useUser();
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { register, handleSubmit, reset, watch: watchIssues } = useForm<FormInput>();
|
const { register, handleSubmit, reset, watch: watchIssues } = useForm<FormInput>();
|
||||||
@ -54,16 +50,73 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newBlocked = [...watch("blocked_list"), ...data.issue_ids];
|
|
||||||
submitChanges({ blocked_list: newBlocked });
|
data.issue_ids.map((issue) => {
|
||||||
handleClose();
|
if (!activeWorkspace || !activeProject || !issueDetail) return;
|
||||||
|
|
||||||
|
const currentBlockers =
|
||||||
|
issues?.results
|
||||||
|
.find((i) => i.id === issue)
|
||||||
|
?.blocker_issues.map((b) => b.blocker_issue_detail?.id ?? "") ?? [];
|
||||||
|
|
||||||
|
issuesService
|
||||||
|
.patchIssue(activeWorkspace.slug, activeProject.id, issue, {
|
||||||
|
blockers_list: [...currentBlockers, issueDetail.id],
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
mutateIssues((prevData) => ({
|
||||||
|
...(prevData as IssueResponse),
|
||||||
|
results: (prevData?.results ?? []).map((issue) => {
|
||||||
|
if (issue.id === issueDetail.id) {
|
||||||
|
return { ...issue, ...response };
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeBlocked = (issueId: string) => {
|
||||||
|
if (!activeWorkspace || !activeProject || !issueDetail) return;
|
||||||
|
|
||||||
|
const currentBlockers =
|
||||||
|
issues?.results
|
||||||
|
.find((i) => i.id === issueId)
|
||||||
|
?.blocker_issues.map((b) => b.blocker_issue_detail?.id ?? "") ?? [];
|
||||||
|
|
||||||
|
const updatedBlockers = currentBlockers.filter((b) => b !== issueDetail.id);
|
||||||
|
|
||||||
|
issuesService
|
||||||
|
.patchIssue(activeWorkspace.slug, activeProject.id, issueId, {
|
||||||
|
blockers_list: updatedBlockers,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
mutateIssues((prevData) => ({
|
||||||
|
...(prevData as IssueResponse),
|
||||||
|
results: (prevData?.results ?? []).map((issue) => {
|
||||||
|
if (issue.id === issueDetail.id) {
|
||||||
|
return { ...issue, ...response };
|
||||||
|
}
|
||||||
|
return issue;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start py-2 flex-wrap">
|
<div className="flex items-start py-2 flex-wrap">
|
||||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||||
<UserGroupIcon className="flex-shrink-0 h-4 w-4" />
|
<FlagIcon className="flex-shrink-0 h-4 w-4" />
|
||||||
<p>Blocked issues</p>
|
<p>Blocked by</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:basis-1/2 space-y-1">
|
<div className="sm:basis-1/2 space-y-1">
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex gap-1 flex-wrap">
|
||||||
@ -71,13 +124,8 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
? watch("blocked_list").map((issue) => (
|
? watch("blocked_list").map((issue) => (
|
||||||
<span
|
<span
|
||||||
key={issue}
|
key={issue}
|
||||||
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"
|
className="group flex items-center gap-1 border rounded-2xl text-xs px-1.5 py-0.5 text-red-500 hover:bg-red-50 border-red-500 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => removeBlocked(issue)}
|
||||||
const updatedBlockers = watch("blocked_list").filter((i) => i !== issue);
|
|
||||||
submitChanges({
|
|
||||||
blocked_list: updatedBlockers,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{`${activeProject?.identifier}-${
|
{`${activeProject?.identifier}-${
|
||||||
issues?.results.find((i) => i.id === issue)?.sequence_id
|
issues?.results.find((i) => i.id === issue)?.sequence_id
|
||||||
@ -145,7 +193,10 @@ const SelectBlocked: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
)}
|
)}
|
||||||
<ul className="text-sm text-gray-700">
|
<ul className="text-sm text-gray-700">
|
||||||
{issuesList.map((issue) => {
|
{issuesList.map((issue) => {
|
||||||
if (!watch("blocked_list").includes(issue.id)) {
|
if (
|
||||||
|
!watch("blocked_list").includes(issue.id) &&
|
||||||
|
!watch("blockers_list").includes(issue.id)
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
|
@ -10,12 +10,7 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
// ui
|
// ui
|
||||||
import { Button } from "ui";
|
import { Button } from "ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { FlagIcon, FolderIcon, MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
FolderIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -62,8 +57,8 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-start py-2 flex-wrap">
|
<div className="flex items-start py-2 flex-wrap">
|
||||||
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
|
||||||
<UserGroupIcon className="flex-shrink-0 h-4 w-4" />
|
<FlagIcon className="flex-shrink-0 h-4 w-4" />
|
||||||
<p>Blocker issues</p>
|
<p>Blocking</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:basis-1/2 space-y-1">
|
<div className="sm:basis-1/2 space-y-1">
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex gap-1 flex-wrap">
|
||||||
@ -71,7 +66,7 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
? watch("blockers_list").map((issue) => (
|
? watch("blockers_list").map((issue) => (
|
||||||
<span
|
<span
|
||||||
key={issue}
|
key={issue}
|
||||||
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"
|
className="group flex items-center gap-1 border rounded-2xl text-xs px-1.5 py-0.5 text-yellow-500 hover:bg-yellow-50 border-yellow-500 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedBlockers = watch("blockers_list").filter((i) => i !== issue);
|
const updatedBlockers = watch("blockers_list").filter((i) => i !== issue);
|
||||||
submitChanges({
|
submitChanges({
|
||||||
@ -145,7 +140,10 @@ const SelectBlocker: React.FC<Props> = ({ submitChanges, issuesList, watch }) =>
|
|||||||
)}
|
)}
|
||||||
<ul className="text-sm text-gray-700">
|
<ul className="text-sm text-gray-700">
|
||||||
{issuesList.map((issue) => {
|
{issuesList.map((issue) => {
|
||||||
if (!watch("blockers_list").includes(issue.id)) {
|
if (
|
||||||
|
!watch("blockers_list").includes(issue.id) &&
|
||||||
|
!watch("blocked_list").includes(issue.id)
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
|
@ -12,6 +12,7 @@ import { IIssue } from "types";
|
|||||||
import { classNames } from "constants/common";
|
import { classNames } from "constants/common";
|
||||||
import { PRIORITIES } from "constants/";
|
import { PRIORITIES } from "constants/";
|
||||||
import CustomSelect from "ui/custom-select";
|
import CustomSelect from "ui/custom-select";
|
||||||
|
import { getPriorityIcon } from "constants/global";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: Control<IIssue, any>;
|
control: Control<IIssue, any>;
|
||||||
@ -33,7 +34,18 @@ const SelectPriority: React.FC<Props> = ({ control, submitChanges, watch }) => {
|
|||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label={
|
label={
|
||||||
<span className={classNames(value ? "" : "text-gray-900", "text-left capitalize")}>
|
<span
|
||||||
|
className={classNames(
|
||||||
|
value ? "" : "text-gray-900",
|
||||||
|
"text-left capitalize flex items-center gap-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getPriorityIcon(
|
||||||
|
watch("priority") && watch("priority") !== ""
|
||||||
|
? watch("priority") ?? ""
|
||||||
|
: "None",
|
||||||
|
"text-sm"
|
||||||
|
)}
|
||||||
{watch("priority") && watch("priority") !== "" ? watch("priority") : "None"}
|
{watch("priority") && watch("priority") !== "" ? watch("priority") : "None"}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@ -44,7 +56,10 @@ const SelectPriority: React.FC<Props> = ({ control, submitChanges, watch }) => {
|
|||||||
>
|
>
|
||||||
{PRIORITIES.map((option) => (
|
{PRIORITIES.map((option) => (
|
||||||
<CustomSelect.Option key={option} value={option} className="capitalize">
|
<CustomSelect.Option key={option} value={option} className="capitalize">
|
||||||
|
<>
|
||||||
|
{getPriorityIcon(option, "text-sm")}
|
||||||
{option}
|
{option}
|
||||||
|
</>
|
||||||
</CustomSelect.Option>
|
</CustomSelect.Option>
|
||||||
))}
|
))}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
|
@ -8,7 +8,7 @@ import useSWR from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Listbox, Menu, Transition } from "@headlessui/react";
|
import { Disclosure, Listbox, Menu, Transition } from "@headlessui/react";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner } from "ui";
|
import { CustomMenu, Spinner } from "ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
@ -483,18 +483,8 @@ const ListView: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</Listbox>
|
</Listbox>
|
||||||
)}
|
)}
|
||||||
<Menu as="div" className="relative">
|
<CustomMenu ellipsis>
|
||||||
<Menu.Button
|
<CustomMenu.MenuItem
|
||||||
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={() => {
|
onClick={() => {
|
||||||
setSelectedIssue({
|
setSelectedIssue({
|
||||||
...issue,
|
...issue,
|
||||||
@ -503,23 +493,15 @@ const ListView: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</CustomMenu.MenuItem>
|
||||||
</Menu.Item>
|
<CustomMenu.MenuItem
|
||||||
<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={() => {
|
onClick={() => {
|
||||||
handleDeleteIssue(issue.id);
|
handleDeleteIssue(issue.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete permanently
|
Delete permanently
|
||||||
</button>
|
</CustomMenu.MenuItem>
|
||||||
</div>
|
</CustomMenu>
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Items>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
export const getPriorityIcon = (priority: string) => {
|
export const getPriorityIcon = (priority: string, className: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case "urgent":
|
case "urgent":
|
||||||
return <span className="material-symbols-rounded">signal_cellular_alt</span>;
|
return <span className={`material-symbols-rounded ${className}`}>error</span>;
|
||||||
case "high":
|
case "high":
|
||||||
return <span className="material-symbols-rounded">signal_cellular_alt_2_bar</span>;
|
return <span className={`material-symbols-rounded ${className}`}>signal_cellular_alt</span>;
|
||||||
case "medium":
|
case "medium":
|
||||||
return <span className="material-symbols-rounded">signal_cellular_alt_1_bar</span>;
|
return (
|
||||||
|
<span className={`material-symbols-rounded ${className}`}>signal_cellular_alt_2_bar</span>
|
||||||
|
);
|
||||||
|
case "low":
|
||||||
|
return (
|
||||||
|
<span className={`material-symbols-rounded ${className}`}>signal_cellular_alt_1_bar</span>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <span>N/A</span>;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -92,7 +92,7 @@ class ProjectIssuesServices extends APIService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
cycleId: string,
|
cycleId: string,
|
||||||
data: {
|
data: {
|
||||||
issue: string[];
|
issues: string[];
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return this.post(CYCLE_DETAIL(workspaceSlug, projectId, cycleId), data)
|
return this.post(CYCLE_DETAIL(workspaceSlug, projectId, cycleId), data)
|
||||||
|
@ -90,47 +90,7 @@ const OnBoard: NextPage = () => {
|
|||||||
<div className="w-full md:w-2/3 lg:w-1/3 p-8 rounded-lg">
|
<div className="w-full md:w-2/3 lg:w-1/3 p-8 rounded-lg">
|
||||||
{invitations && workspaces ? (
|
{invitations && workspaces ? (
|
||||||
invitations.length > 0 ? (
|
invitations.length > 0 ? (
|
||||||
<div className="mt-3 sm:mt-5">
|
<div>
|
||||||
<div className="mt-2">
|
|
||||||
<h2 className="text-2xl font-medium mb-4">Join your workspaces</h2>
|
|
||||||
<div className="space-y-2 mb-12">
|
|
||||||
{invitations.map((item) => (
|
|
||||||
<div
|
|
||||||
className="relative flex items-center border px-4 py-2 rounded"
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<div className="ml-3 text-sm flex flex-col items-start w-full">
|
|
||||||
<h3 className="font-medium text-xl text-gray-700">
|
|
||||||
{item.workspace.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm">invited by {item.workspace.owner.first_name}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-x-2 h-5 items-center">
|
|
||||||
<div className="h-full flex items-center gap-x-1">
|
|
||||||
<input
|
|
||||||
id={`${item.id}`}
|
|
||||||
aria-describedby="workspaces"
|
|
||||||
name={`${item.id}`}
|
|
||||||
checked={invitationsRespond.includes(item.id)}
|
|
||||||
value={item.workspace.name}
|
|
||||||
onChange={() => {
|
|
||||||
handleInvitation(
|
|
||||||
item,
|
|
||||||
invitationsRespond.includes(item.id) ? "withdraw" : "accepted"
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
type="checkbox"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-theme focus:ring-indigo-500"
|
|
||||||
/>
|
|
||||||
<label htmlFor={item.id} className="text-sm">
|
|
||||||
Accept
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 className="text-lg font-medium text-gray-900">Workspace Invitations</h2>
|
<h2 className="text-lg font-medium text-gray-900">Workspace Invitations</h2>
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Select invites that you want to accept.
|
Select invites that you want to accept.
|
||||||
|
@ -176,7 +176,7 @@ const SingleCycle: React.FC = () => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
issuesServices
|
issuesServices
|
||||||
.addIssueToCycle(activeWorkspace.slug, activeProject.id, destination.droppableId, {
|
.addIssueToCycle(activeWorkspace.slug, activeProject.id, destination.droppableId, {
|
||||||
issue: [result.draggableId.split(",")[1]],
|
issues: [result.draggableId.split(",")[1]],
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
|
@ -34,7 +34,7 @@ import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deleti
|
|||||||
import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar";
|
import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar";
|
||||||
import IssueActivitySection from "components/project/issues/issue-detail/activity";
|
import IssueActivitySection from "components/project/issues/issue-detail/activity";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, TextArea, HeaderButton, Breadcrumbs, BreadcrumbItem } from "ui";
|
import { Spinner, TextArea, HeaderButton, Breadcrumbs, BreadcrumbItem, CustomMenu } from "ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
@ -215,6 +215,8 @@ const IssueDetail: NextPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("Issue detail", issueDetail);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
noPadding={true}
|
noPadding={true}
|
||||||
@ -531,12 +533,12 @@ const IssueDetail: NextPage = () => {
|
|||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute origin-top-right left-0 mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10">
|
<Menu.Items className="absolute origin-top-right left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10">
|
||||||
<div className="p-1">
|
<div className="py-1">
|
||||||
<Menu.Item as="div">
|
<Menu.Item as="div">
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="text-left p-2 text-gray-900 hover:bg-indigo-50 text-xs whitespace-nowrap w-full"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
setPreloadedData({
|
setPreloadedData({
|
||||||
@ -551,7 +553,7 @@ const IssueDetail: NextPage = () => {
|
|||||||
<Menu.Item as="div">
|
<Menu.Item as="div">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="p-2 text-left text-gray-900 hover:bg-theme hover:text-white rounded-md text-xs whitespace-nowrap"
|
className="p-2 text-left text-gray-900 hover:bg-indigo-50 text-xs whitespace-nowrap"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsAddAsSubIssueOpen(true);
|
setIsAddAsSubIssueOpen(true);
|
||||||
setPreloadedData({
|
setPreloadedData({
|
||||||
|
@ -208,7 +208,7 @@ const ProjectIssues: NextPage = () => {
|
|||||||
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 mr-5 right-1/2 z-10 mt-1 w-screen max-w-xs translate-x-1/2 transform p-3 bg-white rounded-lg shadow-lg overflow-hidden">
|
<Popover.Panel className="absolute mr-5 right-1/2 z-20 mt-1 w-screen max-w-xs translate-x-1/2 transform p-3 bg-white rounded-lg shadow-lg overflow-hidden">
|
||||||
<div className="relative flex flex-col gap-1 gap-y-4">
|
<div className="relative flex flex-col gap-1 gap-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h4 className="text-sm text-gray-600">Group by</h4>
|
<h4 className="text-sm text-gray-600">Group by</h4>
|
||||||
|
Loading…
Reference in New Issue
Block a user