diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index f5b03267c..9a1b96b2a 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -40,8 +40,13 @@ export const AllBoards: React.FC = ({
-
+
{Object.keys(groupedByIssues).map((singleGroup, index) => { + const currentState = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup) + : null; + const stateId = selectedGroup === "state_detail.name" ? states?.find((s) => s.name === singleGroup)?.id ?? null @@ -56,6 +61,7 @@ export const AllBoards: React.FC = ({ | null; groupTitle: string; bgColor?: string; @@ -28,6 +23,7 @@ type Props = { export const BoardHeader: React.FC = ({ groupedByIssues, + currentState, selectedGroup, groupTitle, bgColor, @@ -54,22 +50,19 @@ export const BoardHeader: React.FC = ({ return (
+ {currentState && getStateGroupIcon(currentState.group, "20", "20", bgColor)}

= ({ ? assignees : addSpaceIfCamelCase(groupTitle)}

- {groupedByIssues[groupTitle].length} + + {groupedByIssues[groupTitle].length} +
) : ( - - Add issue - + customButton={ + } - className="mt-1" optionsPosition="left" noBorder > diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index d3c9be75f..b425a97e9 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -184,15 +184,15 @@ export const SingleBoardIssue: React.FC = ({ return (
-
+
{!isNotAllowed && (
{type && !isNotAllowed && ( @@ -214,19 +214,19 @@ export const SingleBoardIssue: React.FC = ({ {properties.key && ( -
+
{issue.project_detail.identifier}-{issue.sequence_id}
)}
{issue.name}
-
+
{properties.priority && selectedGroup !== "priority" && ( void; +}; + +export const ImagePickerPopover: React.FC = ({ label, value, onChange }) => { + const ref = useRef(null); + + const [isOpen, setIsOpen] = useState(false); + const [searchParams, setSearchParams] = useState(""); + const [formData, setFormData] = useState({ + search: "", + }); + + const { data: images } = useSWR(`UNSPLASH_IMAGES_${searchParams}`, () => + fileService.getUnsplashImages(1, searchParams) + ); + + useOutsideClickDetector(ref, () => { + setIsOpen(false); + }); + + useEffect(() => { + if (!images || value !== null) return; + onChange(images[0].urls.regular); + }, [value, onChange, images]); + + return ( + + setIsOpen((prev) => !prev)} + > + {label} + + + +
+ + + {tabOptions.map((tab) => ( + + `rounded py-1 px-4 text-center text-sm outline-none transition-colors ${ + selected ? "bg-theme text-white" : "text-black" + }` + } + > + {tab.title} + + ))} + + + +
{ + e.preventDefault(); + setSearchParams(formData.search); + }} + className="flex gap-x-2 pt-7" + > + setFormData({ ...formData, search: e.target.value })} + placeholder="Search for images" + /> + + Search + +
+ {images ? ( +
+ {images.map((image) => ( +
+ {image.alt_description} { + setIsOpen(false); + onChange(image.urls.regular); + }} + /> +
+ ))} +
+ ) : ( +
+ +
+ )} +
+ +

Coming Soon...

+
+
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/core/index.ts b/apps/app/components/core/index.ts index 01a190d07..c2d2f8929 100644 --- a/apps/app/components/core/index.ts +++ b/apps/app/components/core/index.ts @@ -9,3 +9,4 @@ export * from "./issues-view"; export * from "./link-modal"; export * from "./not-authorized-view"; export * from "./multi-level-select"; +export * from "./image-picker-popover"; diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 98526a2b0..2cefcfcf5 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -371,13 +371,13 @@ export const IssuesView: React.FC = ({
- + Drop issue here to delete
)} diff --git a/apps/app/components/cycles/completed-cycles-list.tsx b/apps/app/components/cycles/completed-cycles-list.tsx new file mode 100644 index 000000000..899a17bb2 --- /dev/null +++ b/apps/app/components/cycles/completed-cycles-list.tsx @@ -0,0 +1,82 @@ +// react +import { useState } from "react"; +// next +import { useRouter } from "next/router"; +import useSWR from "swr"; + +// components +import { DeleteCycleModal, SingleCycleCard } from "components/cycles"; +// types +import { ICycle, SelectCycleType } from "types"; +import { CompletedCycleIcon } from "components/icons"; +import cyclesService from "services/cycles.service"; +import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys"; + +export interface CompletedCyclesListProps { + setCreateUpdateCycleModal: React.Dispatch>; + setSelectedCycle: React.Dispatch>; +} + +export const CompletedCyclesList: React.FC = ({ + setCreateUpdateCycleModal, + setSelectedCycle, +}) => { + const [cycleDeleteModal, setCycleDeleteModal] = useState(false); + const [selectedCycleForDelete, setSelectedCycleForDelete] = useState(); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: completedCycles } = useSWR( + workspaceSlug && projectId ? CYCLE_COMPLETE_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => cyclesService.getCompletedCycles(workspaceSlug as string, projectId as string) + : null + ); + + const handleDeleteCycle = (cycle: ICycle) => { + setSelectedCycleForDelete({ ...cycle, actionType: "delete" }); + setCycleDeleteModal(true); + }; + + const handleEditCycle = (cycle: ICycle) => { + setSelectedCycle({ ...cycle, actionType: "edit" }); + setCreateUpdateCycleModal(true); + }; + + return ( + <> + {completedCycles && ( + <> + + {completedCycles?.completed_cycles.length > 0 ? ( + completedCycles.completed_cycles.map((cycle) => ( + handleDeleteCycle(cycle)} + handleEditCycle={() => handleEditCycle(cycle)} + /> + )) + ) : ( +
+ +

+ No completed cycles yet. Create with{" "} +
Q
. +

+
+ )} + + )} + + ); +}; diff --git a/apps/app/components/cycles/cycles-list-view.tsx b/apps/app/components/cycles/cycles-list.tsx similarity index 97% rename from apps/app/components/cycles/cycles-list-view.tsx rename to apps/app/components/cycles/cycles-list.tsx index 8491190e8..e55f0e6f1 100644 --- a/apps/app/components/cycles/cycles-list-view.tsx +++ b/apps/app/components/cycles/cycles-list.tsx @@ -13,7 +13,7 @@ type TCycleStatsViewProps = { type: "current" | "upcoming" | "completed"; }; -export const CyclesListView: React.FC = ({ +export const CyclesList: React.FC = ({ cycles, setCreateUpdateCycleModal, setSelectedCycle, diff --git a/apps/app/components/cycles/form.tsx b/apps/app/components/cycles/form.tsx index 58f57ba14..863b9b57a 100644 --- a/apps/app/components/cycles/form.tsx +++ b/apps/app/components/cycles/form.tsx @@ -1,11 +1,16 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +// toast +import useToast from "hooks/use-toast"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // ui import { Button, CustomDatePicker, CustomSelect, Input, TextArea } from "components/ui"; // types import { ICycle } from "types"; +// services +import cyclesService from "services/cycles.service"; type Props = { handleFormSubmit: (values: Partial) => Promise; @@ -17,17 +22,24 @@ type Props = { const defaultValues: Partial = { name: "", description: "", - status: "draft", - start_date: "", - end_date: "", + start_date: null, + end_date: null, }; export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, status, data }) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { setToastAlert } = useToast(); + + const [isDateValid, setIsDateValid] = useState(true); + const { register, formState: { errors, isSubmitting }, handleSubmit, control, + watch, reset, } = useForm({ defaultValues, @@ -41,6 +53,31 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat }); }; + const dateChecker = async (payload: any) => { + await cyclesService + .cycleDateCheck(workspaceSlug as string, projectId as string, payload) + .then((res) => { + if (res.status) { + setIsDateValid(true); + } else { + setIsDateValid(false); + setToastAlert({ + type: "error", + title: "Error!", + message: + "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates", + }); + } + }) + .catch((err) => { + console.log(err); + }); + }; + + const checkEmptyDate = + (watch("start_date") === "" && watch("end_date") === "") || + (!watch("start_date") && !watch("end_date")); + useEffect(() => { reset({ ...defaultValues, @@ -84,30 +121,7 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat register={register} />
-
-
Status
- ( - {field.value ?? "Select Status"}} - input - > - {[ - { label: "Draft", value: "draft" }, - { label: "Started", value: "started" }, - { label: "Completed", value: "completed" }, - ].map((item) => ( - - {item.label} - - ))} - - )} - /> -
+
Start Date
@@ -115,12 +129,19 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat ( { + onChange(val); + watch("end_date") + ? dateChecker({ + start_date: val, + end_date: watch("end_date"), + }) + : ""; + }} error={errors.start_date ? true : false} /> )} @@ -136,12 +157,19 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat ( { + onChange(val); + watch("start_date") + ? dateChecker({ + start_date: watch("start_date"), + end_date: val, + }) + : ""; + }} error={errors.end_date ? true : false} /> )} @@ -158,7 +186,18 @@ export const CycleForm: React.FC = ({ handleFormSubmit, handleClose, stat -
diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx index 711be870d..4f254408a 100644 --- a/apps/app/components/emoji-icon-picker/index.tsx +++ b/apps/app/components/emoji-icon-picker/index.tsx @@ -44,7 +44,7 @@ const EmojiIconPicker: React.FC = ({ label, value, onChange }) => { return ( setIsOpen((prev) => !prev)} > {label} diff --git a/apps/app/components/icons/assignment-clipboard-icon.tsx b/apps/app/components/icons/assignment-clipboard-icon.tsx new file mode 100644 index 000000000..8b9af676e --- /dev/null +++ b/apps/app/components/icons/assignment-clipboard-icon.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const AssignmentClipboardIcon: React.FC = ({ + width = "24", + height = "24", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/backlog-state-icon.tsx b/apps/app/components/icons/backlog-state-icon.tsx new file mode 100644 index 000000000..48ae09524 --- /dev/null +++ b/apps/app/components/icons/backlog-state-icon.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const BacklogStateIcon: React.FC = ({ + width = "20", + height = "20", + className, + color = "#858e96", +}) => ( + + + +); diff --git a/apps/app/components/icons/cancelled-state-icon.tsx b/apps/app/components/icons/cancelled-state-icon.tsx new file mode 100644 index 000000000..5829146ff --- /dev/null +++ b/apps/app/components/icons/cancelled-state-icon.tsx @@ -0,0 +1,78 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const CancelledStateIcon: React.FC = ({ + width = "20", + height = "20", + className, + color = "#f2655a", +}) => ( + + + + + + + + + + + + + +); diff --git a/apps/app/components/icons/completed-state-icon.tsx b/apps/app/components/icons/completed-state-icon.tsx new file mode 100644 index 000000000..584245d58 --- /dev/null +++ b/apps/app/components/icons/completed-state-icon.tsx @@ -0,0 +1,69 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const CompletedStateIcon: React.FC = ({ + width = "20", + height = "20", + className, + color = "#438af3", +}) => ( + + + + + + + + + + + + +); diff --git a/apps/app/components/icons/contrast-icon.tsx b/apps/app/components/icons/contrast-icon.tsx new file mode 100644 index 000000000..5dfa47e4d --- /dev/null +++ b/apps/app/components/icons/contrast-icon.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const ContrastIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + + +); diff --git a/apps/app/components/icons/grid-view-icons.tsx b/apps/app/components/icons/grid-view-icons.tsx new file mode 100644 index 000000000..f5e81daf8 --- /dev/null +++ b/apps/app/components/icons/grid-view-icons.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const GridViewIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/heartbeat-icon.tsx b/apps/app/components/icons/heartbeat-icon.tsx index 8c70fbb87..ff950ca0b 100644 --- a/apps/app/components/icons/heartbeat-icon.tsx +++ b/apps/app/components/icons/heartbeat-icon.tsx @@ -2,21 +2,26 @@ import React from "react"; import type { Props } from "./types"; -export const HeartbeatIcon: React.FC = ({ width = "24", height = "24", className }) => ( - - - - ); +export const HeartbeatIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/index.ts b/apps/app/components/icons/index.ts index dfcb2c5dc..6ec881448 100644 --- a/apps/app/components/icons/index.ts +++ b/apps/app/components/icons/index.ts @@ -1,12 +1,15 @@ export * from "./attachment-icon"; +export * from "./backlog-state-icon"; export * from "./blocked-icon"; export * from "./blocker-icon"; export * from "./bolt-icon"; export * from "./calendar-month-icon"; export * from "./cancel-icon"; +export * from "./cancelled-state-icon"; export * from "./clipboard-icon"; export * from "./comment-icon"; export * from "./completed-cycle-icon"; +export * from "./completed-state-icon"; export * from "./current-cycle-icon"; export * from "./cycle-icon"; export * from "./discord-icon"; @@ -16,6 +19,7 @@ export * from "./ellipsis-horizontal-icon"; export * from "./external-link-icon"; export * from "./github-icon"; export * from "./heartbeat-icon"; +export * from "./started-state-icon"; export * from "./layer-diagonal-icon"; export * from "./lock-icon"; export * from "./menu-icon"; @@ -23,9 +27,17 @@ export * from "./plus-icon"; export * from "./question-mark-circle-icon"; export * from "./setting-icon"; export * from "./signal-cellular-icon"; +export * from "./started-state-icon"; +export * from "./state-group-icon"; export * from "./tag-icon"; export * from "./tune-icon"; +export * from "./unstarted-state-icon"; export * from "./upcoming-cycle-icon"; export * from "./user-group-icon"; export * from "./user-icon-circle"; export * from "./user-icon"; +export * from "./grid-view-icons"; +export * from "./assignment-clipboard-icon"; +export * from "./tick-mark-icon"; +export * from "./contrast-icon"; +export * from "./people-group-icon"; diff --git a/apps/app/components/icons/layer-diagonal-icon.tsx b/apps/app/components/icons/layer-diagonal-icon.tsx index a3eb7a815..aff7bc66c 100644 --- a/apps/app/components/icons/layer-diagonal-icon.tsx +++ b/apps/app/components/icons/layer-diagonal-icon.tsx @@ -6,19 +6,21 @@ export const LayerDiagonalIcon: React.FC = ({ width = "24", height = "24", className, - color = "black", + color = "#858E96", }) => ( - - - - ); + + + +); diff --git a/apps/app/components/icons/people-group-icon.tsx b/apps/app/components/icons/people-group-icon.tsx new file mode 100644 index 000000000..813511208 --- /dev/null +++ b/apps/app/components/icons/people-group-icon.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const PeopleGroupIcon: React.FC = ({ + width = "24", + height = "16", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/setting-icon.tsx b/apps/app/components/icons/setting-icon.tsx index 1c572f977..1451be9b9 100644 --- a/apps/app/components/icons/setting-icon.tsx +++ b/apps/app/components/icons/setting-icon.tsx @@ -2,18 +2,23 @@ import React from "react"; import type { Props } from "./types"; -export const SettingIcon: React.FC = ({ width = "24", height = "24", className }) => ( - - - - ); +export const SettingIcon: React.FC = ({ + width = "24", + height = "24", + color = "black", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/started-state-icon.tsx b/apps/app/components/icons/started-state-icon.tsx new file mode 100644 index 000000000..20de01537 --- /dev/null +++ b/apps/app/components/icons/started-state-icon.tsx @@ -0,0 +1,77 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const StartedStateIcon: React.FC = ({ + width = "20", + height = "20", + className, + color = "#fbb040", +}) => ( + + + + + + + + + + + + +); diff --git a/apps/app/components/icons/state-group-icon.tsx b/apps/app/components/icons/state-group-icon.tsx new file mode 100644 index 000000000..17b13f0b2 --- /dev/null +++ b/apps/app/components/icons/state-group-icon.tsx @@ -0,0 +1,29 @@ +import { + BacklogStateIcon, + CancelledStateIcon, + CompletedStateIcon, + StartedStateIcon, + UnstartedStateIcon, +} from "components/icons"; + +export const getStateGroupIcon = ( + stateGroup: "backlog" | "unstarted" | "started" | "completed" | "cancelled", + width = "20", + height = "20", + color?: string +) => { + switch (stateGroup) { + case "backlog": + return ; + case "unstarted": + return ; + case "started": + return ; + case "completed": + return ; + case "cancelled": + return ; + default: + return <>; + } +}; diff --git a/apps/app/components/icons/tick-mark-icon.tsx b/apps/app/components/icons/tick-mark-icon.tsx new file mode 100644 index 000000000..3d7c1618c --- /dev/null +++ b/apps/app/components/icons/tick-mark-icon.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const TickMarkIcon: React.FC = ({ + width = "24", + height = "24", + color = "#858E96", + className, +}) => ( + + + +); diff --git a/apps/app/components/icons/unstarted-state-icon.tsx b/apps/app/components/icons/unstarted-state-icon.tsx new file mode 100644 index 000000000..0fcad39ea --- /dev/null +++ b/apps/app/components/icons/unstarted-state-icon.tsx @@ -0,0 +1,59 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const UnstartedStateIcon: React.FC = ({ + width = "20", + height = "20", + className, + color = "#858e96", +}) => ( + + + + + + + + + + +); diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 11675bac8..d8dad77a2 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 000000000..0760b0214 --- /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 4338b3162..a21a1cbbb 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 2b810b30a..d762b2bc0 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 1347e2765..4a3c5fdba 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 269b0af73..41f498ec0 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/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index 52a911f5e..f47ea934d 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -23,34 +23,38 @@ export const ViewPrioritySelect: React.FC = ({ isNotAllowed, }) => ( - - {getPriorityIcon( - issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", - "text-sm" - )} - - - } value={issue.state} onChange={(data: string) => { partialUpdateIssue({ priority: data }); }} maxHeight="md" - buttonClassName={`flex ${ - isNotAllowed ? "cursor-not-allowed" : "cursor-pointer" - } items-center gap-x-2 rounded px-2 py-0.5 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ - issue.priority === "urgent" - ? "bg-red-100 text-red-600 hover:bg-red-100" - : issue.priority === "high" - ? "bg-orange-100 text-orange-500 hover:bg-orange-100" - : issue.priority === "medium" - ? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100" - : issue.priority === "low" - ? "bg-green-100 text-green-500 hover:bg-green-100" - : "bg-gray-100" - } border-none`} + customButton={ + + } noChevron disabled={isNotAllowed} selfPositioned={selfPositioned} diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx index 08a085e49..c39610807 100644 --- a/apps/app/components/project/create-project-modal.tsx +++ b/apps/app/components/project/create-project-modal.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; +import Image from "next/image"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; @@ -7,14 +8,19 @@ import useSWR, { mutate } from "swr"; import { useForm, Controller } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; + // services import projectServices from "services/project.service"; import workspaceService from "services/workspace.service"; // hooks import useToast from "hooks/use-toast"; // ui +import { PrimaryButton } from "components/ui/button/primary-button"; import { Button, Input, TextArea, CustomSelect } from "components/ui"; +// icons +import { XMarkIcon } from "@heroicons/react/24/outline"; // components +import { ImagePickerPopover } from "components/core"; import EmojiIconPicker from "components/emoji-icon-picker"; // helpers import { getRandomEmoji } from "helpers/common.helper"; @@ -36,6 +42,7 @@ const defaultValues: Partial = { description: "", network: 2, icon: getRandomEmoji(), + cover_image: null, }; const IsGuestCondition: React.FC<{ @@ -172,127 +179,143 @@ export const CreateProjectModal: React.FC = (props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
-
- - Create Project - -
-

- Create a new project to start working on it. -

+ +
+ {watch("cover_image") !== null && ( + cover image + )} + +
+ +
+
+
+

Create Project

+
+ { + setValue("cover_image", image); + }} + value={watch("cover_image")} + />
-
-
-
- - ( - - )} - /> -
-
- -
-
+
+
+ +
+
-
Network
- ( - k === value.toString()) - ? NETWORK_CHOICES[ - value.toString() as keyof typeof NETWORK_CHOICES - ] - : "Select network" - } - input - > - {Object.keys(NETWORK_CHOICES).map((key) => ( - - {NETWORK_CHOICES[key as keyof typeof NETWORK_CHOICES]} - - ))} - - )} + { + setValue("icon", emoji); + }} + value={watch("icon")} />
-
-