fixed merge conflict
This commit is contained in:
commit
5224b3da88
34 changed files with 3869 additions and 429 deletions
|
|
@ -61,9 +61,6 @@ const SingleBoard: React.FC<Props> = ({
|
|||
// Collapse/Expand
|
||||
const [show, setState] = useState<any>(true);
|
||||
|
||||
// Edit state name
|
||||
const [showInput, setInput] = useState<any>(false);
|
||||
|
||||
if (selectedGroup === "priority")
|
||||
groupTitle === "high"
|
||||
? (bgColor = "#dc2626")
|
||||
|
|
@ -86,54 +83,52 @@ const SingleBoard: React.FC<Props> = ({
|
|||
<div className={`${!show ? "" : "h-full space-y-3 overflow-y-auto flex flex-col"}`}>
|
||||
<div
|
||||
className={`flex justify-between p-3 pb-0 ${
|
||||
snapshot.isDragging ? "bg-indigo-50 border-indigo-100 border-b" : ""
|
||||
} ${!show ? "flex-col bg-gray-50 rounded-md border" : ""}`}
|
||||
!show ? "flex-col bg-gray-50 rounded-md border" : ""
|
||||
}`}
|
||||
>
|
||||
{showInput ? null : (
|
||||
<div className={`flex items-center ${!show ? "flex-col gap-2" : "gap-1"}`}>
|
||||
<button
|
||||
type="button"
|
||||
{...provided.dragHandleProps}
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
!show ? "" : "rotate-90"
|
||||
} ${selectedGroup !== "state_detail.name" ? "hidden" : ""}`}
|
||||
>
|
||||
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600" />
|
||||
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600 mt-[-0.7rem]" />
|
||||
</button>
|
||||
<div
|
||||
className={`flex items-center gap-x-1 px-2 bg-slate-900 rounded-md cursor-pointer ${
|
||||
!show ? "py-2 mb-2 flex-col gap-y-2" : ""
|
||||
}`}
|
||||
<div className={`flex items-center ${!show ? "flex-col gap-2" : "gap-1"}`}>
|
||||
<button
|
||||
type="button"
|
||||
{...provided.dragHandleProps}
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
!show ? "" : "rotate-90"
|
||||
} ${selectedGroup !== "state_detail.name" ? "hidden" : ""}`}
|
||||
>
|
||||
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600" />
|
||||
<EllipsisHorizontalIcon className="h-4 w-4 text-gray-600 mt-[-0.7rem]" />
|
||||
</button>
|
||||
<div
|
||||
className={`flex items-center gap-x-1 px-2 bg-slate-900 rounded-md cursor-pointer ${
|
||||
!show ? "py-2 mb-2 flex-col gap-y-2" : ""
|
||||
}`}
|
||||
style={{
|
||||
border: `2px solid ${bgColor}`,
|
||||
backgroundColor: `${bgColor}20`,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`w-3 h-3 block rounded-full ${!show ? "" : "mr-1"}`}
|
||||
style={{
|
||||
border: `2px solid ${bgColor}`,
|
||||
backgroundColor: `${bgColor}20`,
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
/>
|
||||
<h2
|
||||
className={`text-[0.9rem] font-medium capitalize`}
|
||||
style={{
|
||||
writingMode: !show ? "vertical-rl" : "horizontal-tb",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`w-3 h-3 block rounded-full ${!show ? "" : "mr-1"}`}
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
/>
|
||||
<h2
|
||||
className={`text-[0.9rem] font-medium capitalize`}
|
||||
style={{
|
||||
writingMode: !show ? "vertical-rl" : "horizontal-tb",
|
||||
}}
|
||||
>
|
||||
{groupTitle === null || groupTitle === "null"
|
||||
? "None"
|
||||
: createdBy
|
||||
? createdBy
|
||||
: addSpaceIfCamelCase(groupTitle)}
|
||||
</h2>
|
||||
<span className="text-gray-500 text-sm ml-0.5">
|
||||
{groupedByIssues[groupTitle].length}
|
||||
</span>
|
||||
</div>
|
||||
{groupTitle === null || groupTitle === "null"
|
||||
? "None"
|
||||
: createdBy
|
||||
? createdBy
|
||||
: addSpaceIfCamelCase(groupTitle)}
|
||||
</h2>
|
||||
<span className="text-gray-500 text-sm ml-0.5">
|
||||
{groupedByIssues[groupTitle].length}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={`flex items-center ${!show ? "flex-col pb-2" : ""}`}>
|
||||
<button
|
||||
|
|
@ -141,7 +136,6 @@ const SingleBoard: React.FC<Props> = ({
|
|||
className="h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none"
|
||||
onClick={() => {
|
||||
setState(!show);
|
||||
setInput(false);
|
||||
}}
|
||||
>
|
||||
{show ? (
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
|
||||
const { sprints } = useUser();
|
||||
const { cycles } = useUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -35,7 +35,7 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
|
|||
<Listbox.Button className="flex items-center gap-1 hover:bg-gray-100 relative border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm duration-300">
|
||||
<ArrowPathIcon className="h-3 w-3" />
|
||||
<span className="block truncate">
|
||||
{sprints?.find((i) => i.id.toString() === value?.toString())?.name ?? "Cycle"}
|
||||
{cycles?.find((i) => i.id.toString() === value?.toString())?.name ?? "Cycle"}
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
|
|
@ -48,10 +48,10 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
|
|||
>
|
||||
<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">
|
||||
{sprints?.map((sprint) => (
|
||||
{cycles?.map((cycle) => (
|
||||
<Listbox.Option
|
||||
key={sprint.id}
|
||||
value={sprint.id}
|
||||
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"
|
||||
|
|
@ -61,7 +61,7 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
|
|||
{({ active, selected }) => (
|
||||
<>
|
||||
<span className={`block ${selected && "font-semibold"}`}>
|
||||
{sprint.name}
|
||||
{cycle.name}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
// swr
|
||||
import useSWR from "swr";
|
||||
// headless ui
|
||||
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { classNames, copyTextToClipboard } from "constants/common";
|
||||
// ui
|
||||
import { Input, Button, Spinner } from "ui";
|
||||
import { Popover } from "@headlessui/react";
|
||||
// icons
|
||||
import {
|
||||
UserIcon,
|
||||
|
|
@ -37,6 +38,7 @@ import {
|
|||
// types
|
||||
import type { Control } from "react-hook-form";
|
||||
import type { IIssue, IIssueLabels, IssueResponse, IState, WorkspaceMember } from "types";
|
||||
import { TwitterPicker } from "react-color";
|
||||
|
||||
type Props = {
|
||||
control: Control<IIssue, any>;
|
||||
|
|
@ -48,6 +50,7 @@ const PRIORITIES = ["high", "medium", "low"];
|
|||
|
||||
const defaultValues: Partial<IIssueLabels> = {
|
||||
name: "",
|
||||
colour: "#ff0000",
|
||||
};
|
||||
|
||||
const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDetail }) => {
|
||||
|
|
@ -86,6 +89,8 @@ const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDeta
|
|||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
reset,
|
||||
watch,
|
||||
control: controlLabel,
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
|
|
@ -101,67 +106,79 @@ const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDeta
|
|||
});
|
||||
};
|
||||
|
||||
const sidebarOptions = [
|
||||
{
|
||||
label: "Priority",
|
||||
name: "priority",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: ChartBarIcon,
|
||||
options: PRIORITIES.map((property) => ({
|
||||
label: property,
|
||||
value: property,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
name: "state",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: Squares2X2Icon,
|
||||
options: states?.map((state) => ({
|
||||
label: state.name,
|
||||
value: state.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Cycle",
|
||||
name: "cycle",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: ArrowPathIcon,
|
||||
options: cycles?.map((cycle) => ({
|
||||
label: cycle.name,
|
||||
value: cycle.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Assignees",
|
||||
name: "assignees_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserGroupIcon,
|
||||
options: people?.map((person) => ({
|
||||
label: person.member.first_name,
|
||||
value: person.member.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Blocker",
|
||||
name: "blockers_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserIcon,
|
||||
options: projectIssues?.results?.map((issue) => ({
|
||||
label: issue.name,
|
||||
value: issue.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Blocked",
|
||||
name: "blocked_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserIcon,
|
||||
options: projectIssues?.results?.map((issue) => ({
|
||||
label: issue.name,
|
||||
value: issue.id,
|
||||
})),
|
||||
},
|
||||
const sidebarSections = [
|
||||
[
|
||||
{
|
||||
label: "Status",
|
||||
name: "state",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: Squares2X2Icon,
|
||||
options: states?.map((state) => ({
|
||||
label: state.name,
|
||||
value: state.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Assignees",
|
||||
name: "assignees_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserGroupIcon,
|
||||
options: people?.map((person) => ({
|
||||
label: person.member.first_name,
|
||||
value: person.member.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Priority",
|
||||
name: "priority",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: ChartBarIcon,
|
||||
options: PRIORITIES.map((property) => ({
|
||||
label: property,
|
||||
value: property,
|
||||
})),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
label: "Blocker",
|
||||
name: "blockers_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserIcon,
|
||||
options: projectIssues?.results?.map((issue) => ({
|
||||
label: issue.name,
|
||||
value: issue.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Blocked",
|
||||
name: "blocked_list",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserIcon,
|
||||
options: projectIssues?.results?.map((issue) => ({
|
||||
label: issue.name,
|
||||
value: issue.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "Due Date",
|
||||
name: "target_date",
|
||||
canSelectMultipleOptions: true,
|
||||
icon: UserIcon,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
label: "Cycle",
|
||||
name: "cycle",
|
||||
canSelectMultipleOptions: false,
|
||||
icon: ArrowPathIcon,
|
||||
options: cycles?.map((cycle) => ({
|
||||
label: cycle.name,
|
||||
value: cycle.id,
|
||||
})),
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const handleCycleChange = (cycleId: string) => {
|
||||
|
|
@ -172,219 +189,291 @@ const IssueDetailSidebar: React.FC<Props> = ({ control, submitChanges, issueDeta
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900">Quick Actions</h3>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="p-2 hover:bg-gray-100 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 duration-300"
|
||||
onClick={() =>
|
||||
copyTextToClipboard(
|
||||
`https://app.plane.so/projects/${activeProject?.id}/issues/${issueDetail?.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<LinkIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="p-2 hover:bg-gray-100 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 duration-300"
|
||||
onClick={() => copyTextToClipboard(`${issueDetail?.id}`)}
|
||||
>
|
||||
<ClipboardDocumentIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{sidebarOptions.map((item) => (
|
||||
<div className="flex items-center justify-between gap-x-2" key={item.label}>
|
||||
<div className="flex items-center gap-x-2 text-sm">
|
||||
<item.icon className="h-4 w-4" />
|
||||
<p>{item.label}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name={item.name as keyof IIssue}
|
||||
render={({ field: { value } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
multiple={item.canSelectMultipleOptions}
|
||||
onChange={(value: any) => {
|
||||
if (item.name === "cycle") handleCycleChange(value);
|
||||
else submitChanges({ [item.name]: value });
|
||||
}}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<Listbox.Button className="relative flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm duration-300">
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate sm:block w-16 text-left",
|
||||
item.label === "Priority" ? "capitalize" : ""
|
||||
)}
|
||||
>
|
||||
{value
|
||||
? Array.isArray(value)
|
||||
? value
|
||||
.map(
|
||||
(i: any) =>
|
||||
item.options?.find((option) => option.value === i)?.label
|
||||
)
|
||||
.join(", ") || item.label
|
||||
: item.options?.find((option) => option.value === value)?.label
|
||||
: "None"}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 right-0 mt-1 w-40 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">
|
||||
{item.options ? (
|
||||
item.options.length > 0 ? (
|
||||
item.options.map((option) => (
|
||||
<Listbox.Option
|
||||
key={option.value}
|
||||
className={({ active, selected }) =>
|
||||
`${
|
||||
active || selected
|
||||
? "text-white bg-theme"
|
||||
: "text-gray-900"
|
||||
} ${
|
||||
item.label === "Priority" && "capitalize"
|
||||
} cursor-pointer select-none relative p-2 rounded-md truncate`
|
||||
}
|
||||
value={option.value}
|
||||
>
|
||||
{option.label}
|
||||
</Listbox.Option>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center">No {item.label}s found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
<div className="h-full w-full divide-y-2 divide-gray-100">
|
||||
<div className="flex justify-between items-center pb-3">
|
||||
<h4 className="text-sm font-medium">
|
||||
{activeProject?.identifier}-{issueDetail?.sequence_id}
|
||||
</h4>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="p-2 hover:bg-gray-100 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 duration-300"
|
||||
onClick={() =>
|
||||
copyTextToClipboard(
|
||||
`https://app.plane.so/projects/${activeProject?.id}/issues/${issueDetail?.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<LinkIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="p-2 hover:bg-gray-100 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 duration-300"
|
||||
onClick={() => copyTextToClipboard(`${issueDetail?.id}`)}
|
||||
>
|
||||
<ClipboardDocumentIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="divide-y-2 divide-gray-100">
|
||||
{sidebarSections.map((section, index) => (
|
||||
<div key={index} className="py-1">
|
||||
{section.map((item) => (
|
||||
<div key={item.label} className="flex justify-between items-center gap-x-2 py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm">
|
||||
<item.icon className="h-4 w-4" />
|
||||
<p>{item.label}</p>
|
||||
</div>
|
||||
<div>
|
||||
{item.name === "target_date" ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
value={value ?? new Date().toString()}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ target_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
className="hover:bg-gray-100 border rounded-md 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"
|
||||
/>
|
||||
)}
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<form className="flex items-center gap-x-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Add new label"
|
||||
register={register}
|
||||
validations={{
|
||||
required: false,
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
+
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<div className="flex items-center gap-x-2 text-sm">
|
||||
<TagIcon className="w-4 h-4" />
|
||||
<p>Label</p>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="labels_list"
|
||||
render={({ field: { value } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
multiple
|
||||
onChange={(value) => submitChanges({ labels_list: value })}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="sr-only">Label</Listbox.Label>
|
||||
<div className="relative">
|
||||
<Listbox.Button className="relative flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm duration-300">
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate capitalize sm:block w-16 text-left"
|
||||
)}
|
||||
>
|
||||
{value && value.length > 0
|
||||
? value
|
||||
.map(
|
||||
(i: string) =>
|
||||
issueLabels?.find((option) => option.id === i)?.name
|
||||
)
|
||||
.join(", ")
|
||||
: "None"}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Listbox.Button>
|
||||
/>
|
||||
) : (
|
||||
<Controller
|
||||
control={control}
|
||||
name={item.name as keyof IIssue}
|
||||
render={({ field: { value } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
multiple={item.canSelectMultipleOptions}
|
||||
onChange={(value: any) => {
|
||||
if (item.name === "cycle") handleCycleChange(value);
|
||||
else submitChanges({ [item.name]: value });
|
||||
}}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<Listbox.Button className="relative flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm duration-300">
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate sm:block w-16 text-left",
|
||||
item.label === "Priority" ? "capitalize" : ""
|
||||
)}
|
||||
>
|
||||
{value
|
||||
? Array.isArray(value)
|
||||
? value
|
||||
.map(
|
||||
(i: any) =>
|
||||
item.options?.find((option) => option.value === i)
|
||||
?.label
|
||||
)
|
||||
.join(", ") || item.label
|
||||
: item.options?.find((option) => option.value === value)
|
||||
?.label
|
||||
: "None"}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 right-0 mt-1 w-40 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">
|
||||
{issueLabels ? (
|
||||
issueLabels.length > 0 ? (
|
||||
issueLabels.map((label: any) => (
|
||||
<Listbox.Option
|
||||
key={label.id}
|
||||
className={({ active, selected }) =>
|
||||
`${
|
||||
active || selected
|
||||
? "text-white bg-theme"
|
||||
: "text-gray-900"
|
||||
} cursor-pointer select-none relative p-2 rounded-md truncate`
|
||||
}
|
||||
value={label.id}
|
||||
>
|
||||
{label.name}
|
||||
</Listbox.Option>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center">No labels found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 right-0 mt-1 w-40 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">
|
||||
{item.options ? (
|
||||
item.options.length > 0 ? (
|
||||
item.options.map((option) => (
|
||||
<Listbox.Option
|
||||
key={option.value}
|
||||
className={({ active, selected }) =>
|
||||
`${
|
||||
active || selected
|
||||
? "text-white bg-theme"
|
||||
: "text-gray-900"
|
||||
} ${
|
||||
item.label === "Priority" && "capitalize"
|
||||
} cursor-pointer select-none relative p-2 rounded-md truncate`
|
||||
}
|
||||
value={option.value}
|
||||
>
|
||||
{option.label}
|
||||
</Listbox.Option>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center">No {item.label}s found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-2 pt-3">
|
||||
<h5 className="text-xs font-medium">Add new label</h5>
|
||||
<form className="flex items-center gap-x-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<Popover className="relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`bg-white flex items-center gap-1 rounded-md p-1 outline-none focus:ring-2 focus:ring-indigo-500`}
|
||||
>
|
||||
{watch("colour") && watch("colour") !== "" && (
|
||||
<span
|
||||
className="w-6 h-6 rounded"
|
||||
style={{
|
||||
backgroundColor: watch("colour") ?? "green",
|
||||
}}
|
||||
></span>
|
||||
)}
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</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 z-10 transform right-0 mt-1 px-2 max-w-xs sm:px-0">
|
||||
<Controller
|
||||
name="colour"
|
||||
control={controlLabel}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<TwitterPicker color={value} onChange={(value) => onChange(value.hex)} />
|
||||
)}
|
||||
/>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Title"
|
||||
register={register}
|
||||
validations={{
|
||||
required: "This is required",
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
+
|
||||
</Button>
|
||||
</form>
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<div className="flex items-center gap-x-2 text-sm">
|
||||
<TagIcon className="w-4 h-4" />
|
||||
<p>Label</p>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="labels_list"
|
||||
render={({ field: { value } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
multiple
|
||||
onChange={(value: any) => submitChanges({ labels_list: value })}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="sr-only">Label</Listbox.Label>
|
||||
<div className="relative">
|
||||
<Listbox.Button className="relative flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm duration-300">
|
||||
<span
|
||||
className={classNames(
|
||||
value ? "" : "text-gray-900",
|
||||
"hidden truncate capitalize sm:block w-16 text-left"
|
||||
)}
|
||||
>
|
||||
{value && value.length > 0
|
||||
? value
|
||||
.map(
|
||||
(i: string) =>
|
||||
issueLabels?.find((option) => option.id === i)?.name
|
||||
)
|
||||
.join(", ")
|
||||
: "None"}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={React.Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 right-0 mt-1 w-40 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">
|
||||
{issueLabels ? (
|
||||
issueLabels.length > 0 ? (
|
||||
issueLabels.map((label: IIssueLabels) => (
|
||||
<Listbox.Option
|
||||
key={label.id}
|
||||
className={({ active, selected }) =>
|
||||
`${
|
||||
active || selected
|
||||
? "text-white bg-theme"
|
||||
: "text-gray-900"
|
||||
} flex items-center gap-2 cursor-pointer select-none relative p-2 rounded-md truncate`
|
||||
}
|
||||
value={label.id}
|
||||
>
|
||||
<span
|
||||
className="h-2 w-2 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: label.colour ?? "green" }}
|
||||
></span>
|
||||
{label.name}
|
||||
</Listbox.Option>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center">No labels found</div>
|
||||
)
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// next
|
||||
import Image from "next/image";
|
||||
import {
|
||||
CalendarDaysIcon,
|
||||
ChartBarIcon,
|
||||
ChatBubbleBottomCenterTextIcon,
|
||||
Squares2X2Icon,
|
||||
|
|
@ -19,6 +20,7 @@ const activityIcons = {
|
|||
priority: <ChartBarIcon className="h-4 w-4" />,
|
||||
name: <ChatBubbleBottomCenterTextIcon className="h-4 w-4" />,
|
||||
description: <ChatBubbleBottomCenterTextIcon className="h-4 w-4" />,
|
||||
target_date: <CalendarDaysIcon className="h-4 w-4" />,
|
||||
};
|
||||
|
||||
const IssueActivitySection: React.FC<Props> = ({ issueActivities, states }) => {
|
||||
|
|
@ -64,43 +66,41 @@ const IssueActivitySection: React.FC<Props> = ({ issueActivities, states }) => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full">
|
||||
<div className="w-full text-xs">
|
||||
<p>
|
||||
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
||||
<span className="font-medium">
|
||||
{activity.actor_detail.first_name} {activity.actor_detail.last_name}
|
||||
</span>{" "}
|
||||
<span>{activity.verb}</span>{" "}
|
||||
{activity.verb !== "created" ? (
|
||||
<span>{activity.field ?? "commented"}</span>
|
||||
) : (
|
||||
" this issue"
|
||||
)}
|
||||
<span className="ml-2 text-gray-500">{timeAgo(activity.created_at)}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">{timeAgo(activity.created_at)}</p>
|
||||
<div className="w-full mt-2">
|
||||
{activity.verb !== "created" && (
|
||||
<div className="text-sm">
|
||||
<div>
|
||||
<div>
|
||||
From:{" "}
|
||||
<span className="text-gray-500">
|
||||
{activity.field === "state"
|
||||
? activity.old_value
|
||||
? addSpaceIfCamelCase(
|
||||
states?.find((s) => s.id === activity.old_value)?.name ?? ""
|
||||
)
|
||||
: "None"
|
||||
: activity.old_value}
|
||||
</span>
|
||||
<span className="text-gray-500">From: </span>
|
||||
{activity.field === "state"
|
||||
? activity.old_value
|
||||
? addSpaceIfCamelCase(
|
||||
states?.find((s) => s.id === activity.old_value)?.name ?? ""
|
||||
)
|
||||
: "None"
|
||||
: activity.old_value}
|
||||
</div>
|
||||
<div>
|
||||
To:{" "}
|
||||
<span className="text-gray-500">
|
||||
{activity.field === "state"
|
||||
? activity.new_value
|
||||
? addSpaceIfCamelCase(
|
||||
states?.find((s) => s.id === activity.new_value)?.name ?? ""
|
||||
)
|
||||
: "None"
|
||||
: activity.new_value}
|
||||
</span>
|
||||
<span className="text-gray-500">To: </span>
|
||||
{activity.field === "state"
|
||||
? activity.new_value
|
||||
? addSpaceIfCamelCase(
|
||||
states?.find((s) => s.id === activity.new_value)?.name ?? ""
|
||||
)
|
||||
: "None"
|
||||
: activity.new_value}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue