diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 11675bac85..d8dad77a29 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -14,8 +14,8 @@ import { IssuePrioritySelect, IssueProjectSelect, IssueStateSelect, + IssueDateSelect, } from "components/issues/select"; -import { CycleSelect as IssueCycleSelect } from "components/cycles/select"; import { CreateStateModal } from "components/states"; import { CreateUpdateCycleModal } from "components/cycles"; import { CreateLabelModal } from "components/labels"; @@ -266,16 +266,16 @@ export const IssueForm: FC = ({ /> ( - + )} /> ( - + )} /> = ({ control={control} name="target_date" render={({ field: { value, onChange } }) => ( - + )} /> - ( - - )} - /> = ({ > {({ open }: any) => ( <> - -
- {value && Array.isArray(value) ? : null} -
+ + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } + > + {value && value.length > 0 && Array.isArray(value) ? ( + + + {value.length} Assignees + + ) : ( + + + Assignee + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search for a person..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
{filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate px-2 py-1 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-500` } value={option.value} > - {people && ( - <> - p.member.id === option.value)?.member} - /> - {option.display} - + {({ selected, active }) => ( +
+
+ p.member.id === option.value)?.member} + /> + {option.display} +
+
+ +
+
)}
)) diff --git a/apps/app/components/issues/select/date.tsx b/apps/app/components/issues/select/date.tsx new file mode 100644 index 0000000000..0760b0214a --- /dev/null +++ b/apps/app/components/issues/select/date.tsx @@ -0,0 +1,70 @@ +import React from "react"; + +import { Popover, Transition } from "@headlessui/react"; +import { CalendarDaysIcon, XMarkIcon } from "@heroicons/react/24/outline"; +// react-datepicker +import DatePicker from "react-datepicker"; +// import "react-datepicker/dist/react-datepicker.css"; +import { renderDateFormat } from "helpers/date-time.helper"; + +type Props = { + value: string | null; + onChange: (val: string | null) => void; +}; + +export const IssueDateSelect: React.FC = ({ value, onChange }) => ( + + {({ open }) => ( + <> + + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } + > + + {value ? ( + <> + {value} + + + ) : ( + <> + + Due Date + + )} + + + + + + { + if (!val) onChange(""); + else onChange(renderDateFormat(val)); + }} + dateFormat="dd-MM-yyyy" + inline + /> + + + + )} + +); diff --git a/apps/app/components/issues/select/index.ts b/apps/app/components/issues/select/index.ts index 4338b31629..a21a1cbbb5 100644 --- a/apps/app/components/issues/select/index.ts +++ b/apps/app/components/issues/select/index.ts @@ -4,3 +4,4 @@ export * from "./parent"; export * from "./priority"; export * from "./project"; export * from "./state"; +export * from "./date"; diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index 2b810b30ab..d762b2bc0d 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -7,13 +7,20 @@ import useSWR from "swr"; // headless ui import { Combobox, Transition } from "@headlessui/react"; // icons -import { PlusIcon, RectangleGroupIcon, TagIcon } from "@heroicons/react/24/outline"; +import { + CheckIcon, + MagnifyingGlassIcon, + PlusIcon, + RectangleGroupIcon, + TagIcon, +} from "@heroicons/react/24/outline"; // services import issuesServices from "services/issues.service"; // types import type { IIssueLabels } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; +import { IssueLabelsList } from "components/ui"; type Props = { setIsOpen: React.Dispatch>; @@ -52,36 +59,57 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, > {({ open }: any) => ( <> - Labels + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open + ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " + : "hover:bg-theme/5 " + }` + } > - - - {Array.isArray(value) - ? value.map((v) => issueLabels?.find((l) => l.id === v)?.name).join(", ") || - "Labels" - : issueLabels?.find((l) => l.id === value)?.name || "Labels"} - + {value && value.length > 0 ? ( + + issueLabels?.find((l) => l.id === v)?.color) ?? []} + length={3} + showLength + /> + {value.length} Labels + + ) : ( + + + Label + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search for label..." + displayValue={(assigned: any) => assigned?.name} + /> +
+
{issueLabels && filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((label) => { @@ -92,21 +120,36 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, return ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={label.id} > - - {label.name} + {({ selected }) => ( +
+
+ + {label.name} +
+
+ +
+
+ )}
); } else @@ -119,20 +162,33 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, {children.map((child) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={child.id} > - - {child.name} + {({ selected }) => ( +
+
+ + {child.name} +
+
+ +
+
+ )}
))}
@@ -147,11 +203,13 @@ export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, )}
diff --git a/apps/app/components/issues/select/priority.tsx b/apps/app/components/issues/select/priority.tsx index 1347e27659..4a3c5fdba6 100644 --- a/apps/app/components/issues/select/priority.tsx +++ b/apps/app/components/issues/select/priority.tsx @@ -6,6 +6,7 @@ import { Listbox, Transition } from "@headlessui/react"; import { getPriorityIcon } from "components/icons/priority-icon"; // constants import { PRIORITIES } from "constants/project"; +import { CheckIcon } from "@heroicons/react/24/outline"; type Props = { value: string | null; @@ -16,34 +17,62 @@ export const IssuePrioritySelect: React.FC = ({ value, onChange }) => ( {({ open }) => ( <> - - {getPriorityIcon(value)} -
{value ?? "Priority"}
+ + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" + }` + } + > + + + {getPriorityIcon(value, `${value ? "text-xs" : "text-xs text-gray-500"}`)} + + + {value ?? "Priority"} + + - -
+ +
{PRIORITIES.map((priority) => ( - `${selected ? "bg-indigo-50 font-medium" : ""} ${ - active ? "bg-indigo-50" : "" - } relative cursor-pointer select-none p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={priority} > - - {getPriorityIcon(priority)} - {priority ?? "None"} - + {({ selected, active }) => ( +
+
+ {getPriorityIcon(priority)} + {priority ?? "None"} +
+
+ +
+
+ )}
))}
diff --git a/apps/app/components/issues/select/state.tsx b/apps/app/components/issues/select/state.tsx index 269b0af733..41f498ec0f 100644 --- a/apps/app/components/issues/select/state.tsx +++ b/apps/app/components/issues/select/state.tsx @@ -7,13 +7,19 @@ import useSWR from "swr"; // services import stateService from "services/state.service"; // headless ui -import { Squares2X2Icon, PlusIcon } from "@heroicons/react/24/outline"; +import { + Squares2X2Icon, + PlusIcon, + MagnifyingGlassIcon, + CheckIcon, +} from "@heroicons/react/24/outline"; // icons import { Combobox, Transition } from "@headlessui/react"; // helpers import { getStatesList } from "helpers/state.helper"; // fetch keys import { STATE_LIST } from "constants/fetch-keys"; +import { getStateGroupIcon } from "components/icons"; type Props = { setIsOpen: React.Dispatch>; @@ -41,6 +47,7 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, value: state.id, display: state.name, color: state.color, + group: state.group, })); const filteredOptions = @@ -48,6 +55,7 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, ? options : options?.filter((option) => option.display.toLowerCase().includes(query.toLowerCase())); + const currentOption = options?.find((option) => option.value === value); return ( = ({ setIsOpen, value, onChange, > {({ open }: any) => ( <> - State + `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 + ${ + open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" + }` + } > - - - {value && value !== "" ? ( - option.value === value)?.color, - }} - /> - ) : null} - {options?.find((option) => option.value === value)?.display || "State"} - + {value && value !== "" ? ( + + {currentOption && currentOption.group + ? getStateGroupIcon(currentOption.group, "16", "16", currentOption.color) + : ""} + {currentOption?.display} + + ) : ( + + + {currentOption?.display || "State"} + + )} - setQuery(event.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
+
+ + setQuery(event.target.value)} + placeholder="Search States" + displayValue={(assigned: any) => assigned?.name} + /> +
+
{filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` + className={({ active }) => + `${ + active ? "bg-gray-200" : "" + } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` } value={option.value} > - {states && ( - <> - - {option.display} - - )} + {({ selected, active }) => + states && ( +
+
+ {getStateGroupIcon(option.group, "16", "16", option.color)} + {option.display} +
+
+ +
+
+ ) + }
)) ) : ( @@ -125,11 +149,13 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, )}
diff --git a/apps/app/components/ui/avatar.tsx b/apps/app/components/ui/avatar.tsx index 4726ca8350..2b1c7b2cc4 100644 --- a/apps/app/components/ui/avatar.tsx +++ b/apps/app/components/ui/avatar.tsx @@ -18,7 +18,7 @@ type AvatarProps = { }; export const Avatar: React.FC = ({ user, index }) => ( -
+
{user && user.avatar && user.avatar !== "" ? (
| (Partial | undefined)[] | Partial[]; userIds?: string[]; length?: number; + showLength?: boolean; }; -export const AssigneesList: React.FC = ({ users, userIds, length = 5 }) => { +export const AssigneesList: React.FC = ({ + users, + userIds, + length = 5, + showLength = true, +}) => { const router = useRouter(); const { workspaceSlug } = router.query; @@ -82,7 +88,13 @@ export const AssigneesList: React.FC = ({ users, userIds, len return ; })} - {userIds.length > length ? +{userIds.length - length} : null} + {showLength ? ( + userIds.length > length ? ( + +{userIds.length - length} + ) : null + ) : ( + "" + )} )} diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 04cb34fa3b..ca610a71cf 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -15,3 +15,4 @@ export * from "./progress-bar"; export * from "./select"; export * from "./spinner"; export * from "./tooltip"; +export * from "./labels-list"; diff --git a/apps/app/components/ui/labels-list.tsx b/apps/app/components/ui/labels-list.tsx new file mode 100644 index 0000000000..26257549b3 --- /dev/null +++ b/apps/app/components/ui/labels-list.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +type IssueLabelsListProps = { + labels?: (string | undefined)[]; + length?: number; + showLength?: boolean; +}; + +export const IssueLabelsList: React.FC = ({ + labels, + length = 5, + showLength = true, +}) => ( + <> + {labels && ( + <> + {labels.map((color, index) => ( +
+ +
+ ))} + {labels.length > length ? +{labels.length - length} : null} + + )} + +);