fix: views issues mutation, sidebar link highlight (#1025)

* fix: views issues mutation, sidebar link highlight

* fix: show only specific states when type filter is set

* fix: delete comment mutation

* style: bulk delete issues modal

* fix: project settings features mutation
This commit is contained in:
Aaryan Khandelwal 2023-05-11 02:15:39 +05:30 committed by GitHub
parent 4884ecd668
commit df96d40cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 165 additions and 74 deletions

View File

@ -167,7 +167,7 @@ export const SingleBoard: React.FC<Props> = ({
Add Issue Add Issue
</button> </button>
} }
optionsPosition="left" position="left"
noBorder noBorder
> >
<CustomMenu.MenuItem onClick={addIssueToState}> <CustomMenu.MenuItem onClick={addIssueToState}>

View File

@ -121,7 +121,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-brand-surface-2 shadow-2xl ring-1 ring-black ring-opacity-5 transition-all"> <Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 rounded-xl border border-brand-base bg-brand-base shadow-2xl transition-all">
<form> <form>
<Combobox <Combobox
onChange={(val: string) => { onChange={(val: string) => {
@ -149,7 +149,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
<Combobox.Options <Combobox.Options
static static
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto" className="max-h-80 scroll-py-2 divide-y divide-brand-base overflow-y-auto"
> >
{filteredIssues.length > 0 ? ( {filteredIssues.length > 0 ? (
<li className="p-2"> <li className="p-2">
@ -158,15 +158,15 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
Select issues to delete Select issues to delete
</h2> </h2>
)} )}
<ul className="text-sm text-gray-700"> <ul className="text-sm text-brand-secondary">
{filteredIssues.map((issue) => ( {filteredIssues.map((issue) => (
<Combobox.Option <Combobox.Option
key={issue.id} key={issue.id}
as="div" as="div"
value={issue.id} value={issue.id}
className={({ active }) => className={({ active, selected }) =>
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${ `flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
active ? "bg-gray-900 bg-opacity-5 text-brand-base" : "" active ? "bg-brand-surface-2 text-brand-base" : ""
}` }`
} }
> >
@ -182,7 +182,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
backgroundColor: issue.state_detail.color, backgroundColor: issue.state_detail.color,
}} }}
/> />
<span className="flex-shrink-0 text-xs text-brand-secondary"> <span className="flex-shrink-0 text-xs">
{issue.project_detail.identifier}-{issue.sequence_id} {issue.project_detail.identifier}-{issue.sequence_id}
</span> </span>
<span>{issue.name}</span> <span>{issue.name}</span>

View File

@ -110,7 +110,7 @@ export const ImageUploadModal: React.FC<Props> = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" /> <div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-30 overflow-y-auto"> <div className="fixed inset-0 z-30 overflow-y-auto">
@ -124,7 +124,7 @@ export const ImageUploadModal: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-brand-surface-2 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6"> <Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-brand-base bg-brand-base px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<div className="space-y-5"> <div className="space-y-5">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-brand-base"> <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-brand-base">
Upload Image Upload Image
@ -133,9 +133,9 @@ export const ImageUploadModal: React.FC<Props> = ({
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div <div
{...getRootProps()} {...getRootProps()}
className={`relative block h-80 w-full rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 ${ className={`relative grid h-80 w-full cursor-pointer place-items-center rounded-lg p-12 text-center focus:outline-none focus:ring-2 focus:ring-brand-accent focus:ring-offset-2 ${
(image === null && isDragActive) || !value (image === null && isDragActive) || !value
? "border-2 border-dashed border-brand-base hover:border-gray-400" ? "border-2 border-dashed border-brand-base hover:bg-brand-surface-1"
: "" : ""
}`} }`}
> >
@ -143,7 +143,7 @@ export const ImageUploadModal: React.FC<Props> = ({
<> <>
<button <button
type="button" type="button"
className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-brand-surface-1 px-2 py-0.5 text-xs font-medium text-gray-600" className="absolute top-0 right-0 z-40 translate-x-1/2 -translate-y-1/2 rounded bg-brand-surface-1 px-2 py-0.5 text-xs font-medium text-brand-secondary"
> >
Edit Edit
</button> </button>
@ -152,17 +152,18 @@ export const ImageUploadModal: React.FC<Props> = ({
objectFit="cover" objectFit="cover"
src={image ? URL.createObjectURL(image) : value ? value : ""} src={image ? URL.createObjectURL(image) : value ? value : ""}
alt="image" alt="image"
className="rounded-lg"
/> />
</> </>
) : ( ) : (
<> <div>
<UserCircleIcon className="mx-auto h-16 w-16 text-gray-400" /> <UserCircleIcon className="mx-auto h-16 w-16 text-brand-secondary" />
<span className="mt-2 block text-sm font-medium text-brand-base"> <span className="mt-2 block text-sm font-medium text-brand-secondary">
{isDragActive {isDragActive
? "Drop image here to upload" ? "Drop image here to upload"
: "Drag & drop image here"} : "Drag & drop image here"}
</span> </span>
</> </div>
)} )}
<input {...getInputProps()} type="text" /> <input {...getInputProps()} type="text" />

View File

@ -3,12 +3,12 @@ export * from "./list-view";
export * from "./sidebar"; export * from "./sidebar";
export * from "./bulk-delete-issues-modal"; export * from "./bulk-delete-issues-modal";
export * from "./existing-issues-list-modal"; export * from "./existing-issues-list-modal";
export * from "./filters-list";
export * from "./gpt-assistant-modal"; export * from "./gpt-assistant-modal";
export * from "./image-upload-modal"; export * from "./image-upload-modal";
export * from "./issues-view-filter"; export * from "./issues-view-filter";
export * from "./issues-view"; export * from "./issues-view";
export * from "./link-modal"; export * from "./link-modal";
export * from "./image-picker-popover"; export * from "./image-picker-popover";
export * from "./filter-list";
export * from "./feeds"; export * from "./feeds";
export * from "./theme-switch"; export * from "./theme-switch";

View File

@ -168,7 +168,7 @@ export const SingleList: React.FC<Props> = ({
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
</div> </div>
} }
optionsPosition="right" position="right"
noBorder noBorder
> >
<CustomMenu.MenuItem onClick={addIssueToState}>Create new</CustomMenu.MenuItem> <CustomMenu.MenuItem onClick={addIssueToState}>Create new</CustomMenu.MenuItem>
@ -204,7 +204,8 @@ export const SingleList: React.FC<Props> = ({
makeIssueCopy={() => makeIssueCopy(issue)} makeIssueCopy={() => makeIssueCopy(issue)}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
removeIssue={() => { removeIssue={() => {
if (removeIssue !== null && issue.bridge_id) removeIssue(issue.bridge_id, issue.id); if (removeIssue !== null && issue.bridge_id)
removeIssue(issue.bridge_id, issue.id);
}} }}
isCompleted={isCompleted} isCompleted={isCompleted}
userAuth={userAuth} userAuth={userAuth}

View File

@ -143,6 +143,7 @@ export const JiraGetImportDetail: React.FC = () => {
)} )}
</span> </span>
} }
verticalPosition="top"
> >
{projects.length > 0 ? ( {projects.length > 0 ? (
projects.map((project) => ( projects.map((project) => (

View File

@ -152,6 +152,9 @@ export const IssueActivitySection: React.FC = () => {
const handleCommentDelete = async (commentId: string) => { const handleCommentDelete = async (commentId: string) => {
if (!workspaceSlug || !projectId || !issueId) return; if (!workspaceSlug || !projectId || !issueId) return;
mutateIssueActivities((prevData) => prevData?.filter((p) => p.id !== commentId), false);
await issuesService await issuesService
.deleteIssueComment( .deleteIssueComment(
workspaceSlug as string, workspaceSlug as string,

View File

@ -120,11 +120,11 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
}; };
const handleCycleChange = useCallback( const handleCycleChange = useCallback(
(cycleDetail: ICycle) => { (cycleDetails: ICycle) => {
if (!workspaceSlug || !projectId || !issueDetail) return; if (!workspaceSlug || !projectId || !issueDetail) return;
issuesService issuesService
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleDetail.id, { .addIssueToCycle(workspaceSlug as string, projectId as string, cycleDetails.id, {
issues: [issueDetail.id], issues: [issueDetail.id],
}) })
.then((res) => { .then((res) => {
@ -361,6 +361,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
target_date: val, target_date: val,
}) })
} }
className="bg-brand-surface-1"
disabled={isNotAllowed} disabled={isNotAllowed}
/> />
)} )}

View File

@ -322,7 +322,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue }) => {
Add sub-issue Add sub-issue
</> </>
} }
optionsPosition="left" position="left"
noBorder noBorder
noChevron noChevron
> >

View File

@ -322,7 +322,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
)} )}
<button <button
type="button" type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-brand-surface-2 ${ className={`flex items-center gap-1 rounded bg-brand-surface-1 px-1.5 py-1 text-xs hover:bg-brand-surface-2 ${
iAmFeelingLucky ? "cursor-wait bg-brand-surface-2" : "" iAmFeelingLucky ? "cursor-wait bg-brand-surface-2" : ""
}`} }`}
onClick={handleAutoGenerateDescription} onClick={handleAutoGenerateDescription}
@ -338,7 +338,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
</button> </button>
<button <button
type="button" type="button"
className="-mr-2 flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-brand-surface-2" className="-mr-2 flex items-center gap-1 rounded bg-brand-surface-1 px-1.5 py-1 text-xs hover:bg-brand-surface-2"
onClick={() => setGptAssistantModal((prevData) => !prevData)} onClick={() => setGptAssistantModal((prevData) => !prevData)}
> >
<SparklesIcon className="h-4 w-4" /> <SparklesIcon className="h-4 w-4" />
@ -346,7 +346,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
</button> </button>
<button <button
type="button" type="button"
className="-mr-2 flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-brand-surface-2" className="-mr-2 flex items-center gap-1 rounded bg-brand-surface-1 px-1.5 py-1 text-xs hover:bg-brand-surface-2"
onClick={() => setCreateBlockForm(true)} onClick={() => setCreateBlockForm(true)}
> >
<PencilIcon className="h-3.5 w-3.5" /> <PencilIcon className="h-3.5 w-3.5" />
@ -354,7 +354,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, index
<CustomMenu <CustomMenu
customButton={ customButton={
<button <button
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2.5 py-1 text-left text-xs duration-300 hover:bg-brand-surface-2" className="flex w-full cursor-pointer items-center justify-between gap-1 rounded bg-brand-surface-1 px-2.5 py-1 text-left text-xs duration-300 hover:bg-brand-surface-2"
onClick={() => setIsMenuActive(!isMenuActive)} onClick={() => setIsMenuActive(!isMenuActive)}
> >
<BoltIcon className="h-4.5 w-3.5" /> <BoltIcon className="h-4.5 w-3.5" />

View File

@ -37,8 +37,8 @@ export const ProjectSidebarList: FC = () => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { data: favoriteProjects } = useSWR( const { data: favoriteProjects } = useSWR(
workspaceSlug ? FAVORITE_PROJECTS_LIST(workspaceSlug as string) : null, workspaceSlug ? FAVORITE_PROJECTS_LIST(workspaceSlug.toString()) : null,
() => (workspaceSlug ? projectService.getFavoriteProjects(workspaceSlug as string) : null) () => (workspaceSlug ? projectService.getFavoriteProjects(workspaceSlug.toString()) : null)
); );
const { data: projects } = useSWR( const { data: projects } = useSWR(

View File

@ -17,7 +17,8 @@ type Props = {
textAlignment?: "left" | "center" | "right"; textAlignment?: "left" | "center" | "right";
noBorder?: boolean; noBorder?: boolean;
noChevron?: boolean; noChevron?: boolean;
optionsPosition?: "left" | "right"; position?: "left" | "right";
verticalPosition?: "top" | "bottom";
customButton?: JSX.Element; customButton?: JSX.Element;
}; };
@ -40,7 +41,8 @@ const CustomMenu = ({
textAlignment, textAlignment,
noBorder = false, noBorder = false,
noChevron = false, noChevron = false,
optionsPosition = "right", position = "right",
verticalPosition = "bottom",
customButton, customButton,
}: Props) => ( }: Props) => (
<Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}> <Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
@ -103,9 +105,9 @@ const CustomMenu = ({
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items <Menu.Items
className={`absolute z-20 mt-1 overflow-y-scroll whitespace-nowrap rounded-md border border-brand-base bg-brand-surface-1 p-1 text-xs shadow-lg focus:outline-none ${ className={`absolute z-20 overflow-y-scroll whitespace-nowrap rounded-md border border-brand-base bg-brand-surface-1 p-1 text-xs shadow-lg focus:outline-none ${
optionsPosition === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right" position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
} ${ } ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
height === "sm" height === "sm"
? "max-h-28" ? "max-h-28"
: height === "md" : height === "md"

View File

@ -15,6 +15,7 @@ type CustomSearchSelectProps = {
textAlignment?: "left" | "center" | "right"; textAlignment?: "left" | "center" | "right";
height?: "sm" | "md" | "rg" | "lg"; height?: "sm" | "md" | "rg" | "lg";
position?: "right" | "left"; position?: "right" | "left";
verticalPosition?: "top" | "bottom";
noChevron?: boolean; noChevron?: boolean;
customButton?: JSX.Element; customButton?: JSX.Element;
optionsClassName?: string; optionsClassName?: string;
@ -32,6 +33,7 @@ export const CustomSearchSelect = ({
onChange, onChange,
options, options,
position = "left", position = "left",
verticalPosition = "bottom",
noChevron = false, noChevron = false,
customButton, customButton,
optionsClassName = "", optionsClassName = "",
@ -99,7 +101,9 @@ export const CustomSearchSelect = ({
<Combobox.Options <Combobox.Options
className={`${optionsClassName} absolute min-w-[10rem] border border-brand-base p-2 ${ className={`${optionsClassName} absolute min-w-[10rem] border border-brand-base p-2 ${
position === "right" ? "right-0" : "left-0" position === "right" ? "right-0" : "left-0"
} z-10 mt-1 origin-top-right rounded-md bg-brand-surface-1 text-xs shadow-lg focus:outline-none`} } ${
verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"
} z-10 origin-top-right rounded-md bg-brand-surface-1 text-xs shadow-lg focus:outline-none`}
> >
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-brand-base bg-brand-surface-1 px-2"> <div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-brand-base bg-brand-surface-1 px-2">
<MagnifyingGlassIcon className="h-3 w-3 text-brand-secondary" /> <MagnifyingGlassIcon className="h-3 w-3 text-brand-secondary" />

View File

@ -13,6 +13,7 @@ type CustomSelectProps = {
textAlignment?: "left" | "center" | "right"; textAlignment?: "left" | "center" | "right";
maxHeight?: "sm" | "rg" | "md" | "lg" | "none"; maxHeight?: "sm" | "rg" | "md" | "lg" | "none";
position?: "right" | "left"; position?: "right" | "left";
verticalPosition?: "top" | "bottom";
width?: "auto" | string; width?: "auto" | string;
input?: boolean; input?: boolean;
noChevron?: boolean; noChevron?: boolean;
@ -30,6 +31,7 @@ const CustomSelect = ({
onChange, onChange,
maxHeight = "none", maxHeight = "none",
position = "left", position = "left",
verticalPosition = "bottom",
width = "auto", width = "auto",
input = false, input = false,
noChevron = false, noChevron = false,
@ -80,6 +82,8 @@ const CustomSelect = ({
<Listbox.Options <Listbox.Options
className={`${optionsClassName} absolute border border-brand-base ${ className={`${optionsClassName} absolute border border-brand-base ${
position === "right" ? "right-0" : "left-0" position === "right" ? "right-0" : "left-0"
} ${
verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"
} z-10 mt-1 origin-top-right overflow-y-auto rounded-md bg-brand-surface-1 text-xs shadow-lg focus:outline-none ${ } z-10 mt-1 origin-top-right overflow-y-auto rounded-md bg-brand-surface-1 text-xs shadow-lg focus:outline-none ${
width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width
} ${input ? "max-h-48" : ""} ${ } ${input ? "max-h-48" : ""} ${

View File

@ -18,13 +18,16 @@ const EmptySpace: React.FC<EmptySpaceProps> = ({ title, description, children, I
<div className="max-w-lg"> <div className="max-w-lg">
{Icon ? ( {Icon ? (
<div className="mb-4"> <div className="mb-4">
<Icon className="h-14 w-14 text-gray-400" /> <Icon className="h-14 w-14 text-brand-secondary" />
</div> </div>
) : null} ) : null}
<h2 className="text-lg font-medium text-brand-base">{title}</h2> <h2 className="text-lg font-medium text-brand-base">{title}</h2>
<div className="mt-1 text-sm text-brand-secondary">{description}</div> <div className="mt-1 text-sm text-brand-secondary">{description}</div>
<ul role="list" className="mt-6 divide-y divide-gray-200 border-t border-brand-base border-b"> <ul
role="list"
className="mt-6 divide-y divide-brand-base border-t border-b border-brand-base"
>
{children} {children}
</ul> </ul>
{link ? ( {link ? (
@ -57,7 +60,7 @@ const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Ico
} space-x-3 py-4`} } space-x-3 py-4`}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className={`inline-flex h-10 w-10 items-center justify-center rounded-lg bg-brand-accent`}> <span className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-brand-accent">
<Icon className="h-6 w-6 text-white" aria-hidden="true" /> <Icon className="h-6 w-6 text-white" aria-hidden="true" />
</span> </span>
</div> </div>
@ -67,7 +70,7 @@ const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({ title, description, Ico
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<ChevronRightIcon <ChevronRightIcon
className="h-5 w-5 text-gray-400 group-hover:text-brand-secondary" className="h-5 w-5 text-brand-base group-hover:text-brand-secondary"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>

View File

@ -43,9 +43,9 @@ export const WorkspaceSidebarMenu: React.FC = () => {
<a <a
className={`${ className={`${
( (
link.name === "Dashboard" link.name === "Settings"
? router.asPath === link.href ? router.asPath.includes(link.href)
: router.asPath.includes(link.href) : router.asPath === link.href
) )
? "bg-brand-surface-2 text-brand-base" ? "bg-brand-surface-2 text-brand-base"
: "text-brand-secondary hover:bg-brand-surface-2 hover:text-brand-secondary focus:bg-brand-surface-2 focus:text-brand-secondary" : "text-brand-secondary hover:bg-brand-surface-2 hover:text-brand-secondary focus:bg-brand-surface-2 focus:text-brand-secondary"

View File

@ -106,13 +106,19 @@ export const MODULE_ISSUES_WITH_PARAMS = (moduleId: string, params?: any) => {
const paramsKey = paramsToKey(params); const paramsKey = paramsToKey(params);
return `MODULE_ISSUES_WITH_PARAMS_${moduleId}_${paramsKey.toUpperCase()}`; return `MODULE_ISSUES_WITH_PARAMS_${moduleId.toUpperCase()}_${paramsKey.toUpperCase()}`;
}; };
export const MODULE_DETAILS = (moduleId: string) => `MODULE_DETAILS_${moduleId.toUpperCase()}`; export const MODULE_DETAILS = (moduleId: string) => `MODULE_DETAILS_${moduleId.toUpperCase()}`;
export const VIEWS_LIST = (projectId: string) => `VIEWS_LIST_${projectId.toUpperCase()}`; export const VIEWS_LIST = (projectId: string) => `VIEWS_LIST_${projectId.toUpperCase()}`;
export const VIEW_ISSUES = (viewId: string) => `VIEW_ISSUES_${viewId.toUpperCase()}`;
export const VIEW_DETAILS = (viewId: string) => `VIEW_DETAILS_${viewId.toUpperCase()}`; export const VIEW_DETAILS = (viewId: string) => `VIEW_DETAILS_${viewId.toUpperCase()}`;
export const VIEW_ISSUES = (viewId: string, params: any) => {
if (!params) return `VIEW_ISSUES_${viewId.toUpperCase()}`;
const paramsKey = paramsToKey(params);
return `VIEW_ISSUES_${viewId.toUpperCase()}_${paramsKey.toUpperCase()}`;
};
// Issues // Issues
export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`; export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`;

View File

@ -12,6 +12,8 @@ export const orderArrayBy = (
key: string, key: string,
ordering: "ascending" | "descending" = "ascending" ordering: "ascending" | "descending" = "ascending"
) => { ) => {
if (!array || !Array.isArray(array) || array.length === 0) return [];
if (key[0] === "-") { if (key[0] === "-") {
ordering = "descending"; ordering = "descending";
key = key.slice(1); key = key.slice(1);

View File

@ -21,6 +21,7 @@ import {
MODULE_ISSUES_WITH_PARAMS, MODULE_ISSUES_WITH_PARAMS,
PROJECT_ISSUES_LIST_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS,
STATES_LIST, STATES_LIST,
VIEW_ISSUES,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
const useIssuesView = () => { const useIssuesView = () => {
@ -40,7 +41,7 @@ const useIssuesView = () => {
} = useContext(issueViewContext); } = useContext(issueViewContext);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const params: any = { const params: any = {
order_by: orderBy, order_by: orderBy,
@ -99,6 +100,14 @@ const useIssuesView = () => {
: null : null
); );
const { data: viewIssues } = useSWR(
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
workspaceSlug && projectId && viewId && params
? () =>
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
: null
);
const { data: states } = useSWR( const { data: states } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId
@ -106,7 +115,17 @@ const useIssuesView = () => {
: null : null
); );
const statesList = getStatesList(states ?? {}); const statesList = getStatesList(states ?? {});
const stateIds = statesList.map((state) => state.id); const activeStatesList = statesList.filter(
(state) => state.group === "started" || state.group === "unstarted"
);
const backlogStatesList = statesList.filter((state) => state.group === "backlog");
const stateIds =
filters && filters?.type === "active"
? activeStatesList.map((state) => state.id)
: filters?.type === "backlog"
? backlogStatesList.map((state) => state.id)
: statesList.map((state) => state.id);
const emptyStatesObject: { [key: string]: [] } = {}; const emptyStatesObject: { [key: string]: [] } = {};
for (let i = 0; i < stateIds.length; i++) { for (let i = 0; i < stateIds.length; i++) {
@ -118,7 +137,13 @@ const useIssuesView = () => {
[key: string]: IIssue[]; [key: string]: IIssue[];
} }
| undefined = useMemo(() => { | undefined = useMemo(() => {
const issuesToGroup = cycleId ? cycleIssues : moduleId ? moduleIssues : projectIssues; const issuesToGroup = cycleId
? cycleIssues
: moduleId
? moduleIssues
: viewId
? viewIssues
: projectIssues;
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup }; if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
if (groupByProperty === "state") if (groupByProperty === "state")
@ -129,9 +154,11 @@ const useIssuesView = () => {
projectIssues, projectIssues,
cycleIssues, cycleIssues,
moduleIssues, moduleIssues,
viewIssues,
groupByProperty, groupByProperty,
cycleId, cycleId,
moduleId, moduleId,
viewId,
emptyStatesObject, emptyStatesObject,
]); ]);

View File

@ -1,7 +1,11 @@
import useSWR from "swr";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// helpers
import { orderArrayBy } from "helpers/array.helper";
// fetch-keys // fetch-keys
import { PROJECTS_LIST } from "constants/fetch-keys"; import { PROJECTS_LIST } from "constants/fetch-keys";
@ -20,7 +24,7 @@ const useProjects = () => {
.filter((_item, index) => index < 3); .filter((_item, index) => index < 3);
return { return {
projects: projects || [], projects: orderArrayBy(projects ?? [], "is_favorite", "descending") || [],
recentProjects: recentProjects || [], recentProjects: recentProjects || [],
mutateProjects, mutateProjects,
}; };

View File

@ -30,7 +30,7 @@ const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {
href: `/${workspaceSlug}/settings/integrations`, href: `/${workspaceSlug}/settings/integrations`,
}, },
{ {
label: "Import/ Export", label: "Import/Export",
href: `/${workspaceSlug}/settings/import-export`, href: `/${workspaceSlug}/settings/import-export`,
}, },
]; ];
@ -94,7 +94,11 @@ const SettingsNavbar: React.FC<Props> = ({ profilePage = false }) => {
<a> <a>
<div <div
className={`rounded-3xl border border-brand-base px-5 py-1.5 text-sm sm:px-7 sm:py-2 sm:text-base ${ className={`rounded-3xl border border-brand-base px-5 py-1.5 text-sm sm:px-7 sm:py-2 sm:text-base ${
router.asPath === link.href (
link.label === "Import/Export"
? router.asPath.includes(link.href)
: router.asPath === link.href
)
? "border-brand-accent bg-brand-accent text-white" ? "border-brand-accent bg-brand-accent text-white"
: "border-brand-base bg-brand-surface-2 hover:bg-brand-surface-1" : "border-brand-base bg-brand-surface-2 hover:bg-brand-surface-1"
}`} }`}

View File

@ -164,7 +164,7 @@ const IssueDetailsPage: NextPage = () => {
</a> </a>
</Link> </Link>
<CustomMenu ellipsis optionsPosition="left"> <CustomMenu ellipsis position="left">
{siblingIssues && siblingIssues.length > 0 ? ( {siblingIssues && siblingIssues.length > 0 ? (
siblingIssues.map((issue: IIssue) => ( siblingIssues.map((issue: IIssue) => (
<CustomMenu.MenuItem key={issue.id}> <CustomMenu.MenuItem key={issue.id}>

View File

@ -563,7 +563,7 @@ const SinglePage: NextPage = () => {
</div> </div>
</div> </div>
) : ( ) : (
<Loader> <Loader className="p-8">
<Loader.Item height="200px" /> <Loader.Item height="200px" />
</Loader> </Loader>
)} )}

View File

@ -18,10 +18,10 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons"; import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
import { DocumentTextIcon } from "@heroicons/react/24/outline"; import { DocumentTextIcon } from "@heroicons/react/24/outline";
// types // types
import { IProject } from "types"; import { IFavoriteProject, IProject } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
import { SettingsHeader } from "components/project"; import { SettingsHeader } from "components/project";
const featuresList = [ const featuresList = [
@ -84,16 +84,29 @@ const FeaturesSettings: NextPage = () => {
); );
const handleSubmit = async (formData: Partial<IProject>) => { const handleSubmit = async (formData: Partial<IProject>) => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !projectDetails) return;
mutate<IProject>( if (projectDetails.is_favorite)
PROJECT_DETAILS(projectId as string), mutate<IFavoriteProject[]>(
(prevData) => ({ ...(prevData as IProject), ...formData }), FAVORITE_PROJECTS_LIST(workspaceSlug.toString()),
(prevData) =>
prevData?.map((p) => {
if (p.project === projectId)
return {
...p,
project_detail: {
...p.project_detail,
...formData,
},
};
return p;
}),
false false
); );
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string), PROJECTS_LIST(workspaceSlug.toString()),
(prevData) => (prevData) =>
prevData?.map((p) => { prevData?.map((p) => {
if (p.id === projectId) if (p.id === projectId)
@ -107,19 +120,34 @@ const FeaturesSettings: NextPage = () => {
false false
); );
mutate<IProject>(
PROJECT_DETAILS(projectId as string),
(prevData) => ({ ...(prevData as IProject), ...formData }),
false
);
setToastAlert({
type: "success",
title: "Success!",
message: "Project feature updated successfully.",
});
await projectService await projectService
.updateProject(workspaceSlug as string, projectId as string, formData) .updateProject(workspaceSlug as string, projectId as string, formData)
.then((res) => { .then(() => {
mutate(
projectDetails.is_favorite
? FAVORITE_PROJECTS_LIST(workspaceSlug.toString())
: PROJECTS_LIST(workspaceSlug.toString())
);
mutate(PROJECT_DETAILS(projectId as string)); mutate(PROJECT_DETAILS(projectId as string));
mutate(PROJECTS_LIST(workspaceSlug as string));
setToastAlert({
title: "Success!",
type: "success",
message: "Project features updated successfully.",
});
}) })
.catch((err) => { .catch((err) => {
console.error(err); setToastAlert({
type: "error",
title: "Error!",
message: "Project feature could not be updated. Please try again.",
});
}); });
}; };

View File

@ -229,7 +229,7 @@ const GeneralSettings: NextPage = () => {
</div> </div>
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
{watch("cover_image") ? ( {watch("cover_image") ? (
<div className="h-32 w-full rounded border p-1"> <div className="h-32 w-full rounded border border-brand-base p-1">
<div className="relative h-full w-full rounded"> <div className="relative h-full w-full rounded">
<Image <Image
src={watch("cover_image")!} src={watch("cover_image")!}

View File

@ -95,7 +95,7 @@ const SingleView: React.FC = () => {
document.dispatchEvent(e); document.dispatchEvent(e);
}} }}
> >
<PlusIcon className="w-4 h-4" /> <PlusIcon className="h-4 w-4" />
Add Issue Add Issue
</PrimaryButton> </PrimaryButton>
</div> </div>

View File

@ -119,7 +119,7 @@ const ProjectViews: NextPage = () => {
/> />
) )
) : ( ) : (
<Loader className="space-y-3"> <Loader className="space-y-3 p-8">
<Loader.Item height="30px" /> <Loader.Item height="30px" />
<Loader.Item height="30px" /> <Loader.Item height="30px" />
<Loader.Item height="30px" /> <Loader.Item height="30px" />

View File

@ -19,12 +19,12 @@ import { Loader, EmptyState, PrimaryButton } from "components/ui";
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs"; import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// images
import emptyProject from "public/empty-state/empty-project.svg";
// types // types
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys"; import { PROJECT_MEMBERS } from "constants/fetch-keys";
// image
import emptyProject from "public/empty-state/empty-project.svg";
const ProjectsPage: NextPage = () => { const ProjectsPage: NextPage = () => {
// router // router